diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 28a8815bca..2d905e7e49 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -31,6 +31,14 @@
1.46.1
+
+ org.netbeans.api.templates
+
+
+
+ 1.6.1
+
+ org.netbeans.core
@@ -155,6 +163,14 @@
9.7.1
+
+ org.openide.loaders
+
+
+
+ 7.63.2
+
+ org.openide.modules
@@ -242,6 +258,7 @@
org.sleuthkit.autopsy.eventsorg.sleuthkit.autopsy.externalresultsorg.sleuthkit.autopsy.filesearch
+ org.sleuthkit.autopsy.frameworkorg.sleuthkit.autopsy.ingestorg.sleuthkit.autopsy.keywordsearchserviceorg.sleuthkit.autopsy.menuactions
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java
index 02c2891b88..4ac2e31ab1 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 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java
index 68a1549399..2fefb10d54 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
-* Copyright 2011-2016 Basis Technology Corp.
+* Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java
index 15cacf490d..a8d0979a97 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,27 +25,37 @@ import org.openide.LifecycleManager;
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.casemodule.CaseActionException;
import org.sleuthkit.autopsy.coreutils.Logger;
+/**
+ * The action associated with the Case/Exit menu item. It closes the current
+ * case, if any, and shuts down the application.
+ */
@ActionRegistration(displayName = "Exit", iconInMenu = true)
@ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999)
@ActionID(id = "org.sleuthkit.autopsy.casemodule.ExitAction", category = "Case")
-
final public class ExitAction implements ActionListener {
+ @NbBundle.Messages({
+ "ExitAction.confirmationDialog.title=Ingest is Running",
+ "ExitAction.confirmationDialog.message=Ingest is running, are you sure you want to exit?"
+
+ })
@Override
public void actionPerformed(ActionEvent e) {
- try {
- Case currentCase = Case.getCurrentCase();
- if (currentCase != null) {
- currentCase.closeCase();
- }
- } catch (Exception ex) {
- Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Had a problem closing the case.", ex); //NON-NLS
- } finally {
- LifecycleManager.getDefault().exit();
+ if (IngestRunningCheck.checkAndConfirmProceed(Bundle.ExitAction_confirmationDialog_title(), Bundle.ExitAction_confirmationDialog_message())) {
+ new Thread(() -> {
+ try {
+ Case.closeCurrentCase();
+ } catch (CaseActionException ex) {
+ Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Error closing the current case on exit", ex); //NON-NLS
+ } finally {
+ LifecycleManager.getDefault().exit();
+ }
+ }).start();
}
}
-
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java b/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java
new file mode 100644
index 0000000000..e284ee33a1
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/actions/IngestRunningCheck.java
@@ -0,0 +1,53 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.sleuthkit.autopsy.actions;
+
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+
+/**
+ * A helper for actions that checks to see if ingest is running. If it is,
+ * prompts the user to confirm they want to proceed with whatever operation was
+ * initiated (e.g., closing the case).
+ */
+public class IngestRunningCheck {
+
+ /**
+ * Checks to see if ingest is running. If it is, prompts the user to confirm
+ * they want to proceed with whatever operation was initiated (e.g., closing
+ * the case).
+ *
+ * @param optionsDlgTitle The title for the options dialog used to confirm
+ * the iser's intentions.
+ * @param optionsDlgMessage The message for the options dialog used to
+ * confirm the iser's intentions.
+ *
+ * @return True to proceed, false otherwise.
+ */
+ public static boolean checkAndConfirmProceed(String optionsDlgTitle, String optionsDlgMessage) {
+ if (IngestManager.getInstance().isIngestRunning()) {
+ NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
+ optionsDlgMessage,
+ optionsDlgTitle,
+ NotifyDescriptor.YES_NO_OPTION,
+ NotifyDescriptor.WARNING_MESSAGE);
+ descriptor.setValue(NotifyDescriptor.NO_OPTION);
+ Object response = DialogDisplayer.getDefault().notify(descriptor);
+ return (DialogDescriptor.YES_OPTION == response);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Private contructor to prevent instantiation of a utility class.
+ */
+ private IngestRunningCheck() {
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
index bbadf4291d..62a23b46b0 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
- *
- * Copyright 2014-2015 Basis Technology Corp.
+ *
+ * Copyright 2011-2017 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.
@@ -18,9 +18,9 @@
*/
package org.sleuthkit.autopsy.actions;
+import java.awt.Desktop;
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;
@@ -35,37 +35,58 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * Action in menu to open the folder containing the log files
+ * The action associated with the Help/Open Log Folder menu item. It opens a
+ * file explorer window for either the log subdirectory for the currently open
+ * case, or the log subdirectory of the user directory, if there is no current
+ * case.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
-@ActionRegistration(
- displayName = "#CTL_OpenLogFolder", iconInMenu = true)
+@ActionRegistration(displayName = "#CTL_OpenLogFolder", iconInMenu = true)
@ActionReference(path = "Menu/Help", position = 1750)
@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 {
- File logDir;
- if (Case.isCaseOpen()) {
- logDir = new File(Case.getCurrentCase().getLogDirectoryPath());
- } else {
+ File logDir;
+ if (Case.isCaseOpen()) {
+ try {
+ /*
+ * Open the log directory for the case.
+ */
+ Case currentCase = Case.getCurrentCase();
+ logDir = new File(currentCase.getLogDirectoryPath());
+ } catch (IllegalStateException ex) {
+ /*
+ * There is no open case, open the application level log
+ * directory.
+ */
logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log");
}
- if (logDir.exists() == false) {
- NotifyDescriptor d
- = new NotifyDescriptor.Message(
- NbBundle.getMessage(this.getClass(), "OpenLogFolder.error1", logDir.getAbsolutePath()),
- NotifyDescriptor.ERROR_MESSAGE);
- DialogDisplayer.getDefault().notify(d);
- } else {
+ } else {
+ logDir = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "var" + File.separator + "log");
+ }
+
+ try {
+ if (logDir.exists()) {
Desktop.getDesktop().open(logDir);
+ } else {
+ logger.log(Level.SEVERE, String.format("The log directory %s does not exist", logDir));
+ NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message(
+ NbBundle.getMessage(this.getClass(), "OpenLogFolder.error1", logDir.getAbsolutePath()),
+ NotifyDescriptor.ERROR_MESSAGE);
+ DialogDisplayer.getDefault().notify(notifyDescriptor);
}
} catch (IOException ex) {
- logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "OpenLogFolder.CouldNotOpenLogFolder"), ex); //NON-NLS
-
+ logger.log(Level.SEVERE, String.format("Could not open log directory %s", logDir), ex);
+ NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message(
+ NbBundle.getMessage(this.getClass(), "OpenLogFolder.CouldNotOpenLogFolder", logDir.getAbsolutePath()),
+ NotifyDescriptor.ERROR_MESSAGE);
+ DialogDisplayer.getDefault().notify(notifyDescriptor);
}
}
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java
index d05baf425d..c3477859e6 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
- *
- * Copyright 2015 Basis Technology Corp.
+ *
+ * Copyright 2011-2017 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.
@@ -35,37 +35,45 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * Action in menu to open the folder containing the output files
+ * The action associated with the Tools/Open Output Folder menu item. It opens a
+ * file explorer window for the root output directory for the currently open
+ * case. If the case is a single-user case, this is the case directory. If the
+ * case is a multi-user case, this is a subdirectory of the case directory
+ * specific to the host machine.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
-@ActionRegistration(
- displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy=true)
+@ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = false)
@ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849)
@ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help")
public final class OpenOutputFolderAction extends CallableSystemAction {
+ private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(OpenOutputFolderAction.class.getName());
-
+
@Override
public void performAction() {
-
+ File outputDir;
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 {
+ Case currentCase = Case.getCurrentCase();
+ outputDir = new File(currentCase.getOutputDirectory());
+ if (outputDir.exists()) {
+ try {
Desktop.getDesktop().open(outputDir);
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, String.format("Failed to open output folder %s", outputDir), ex); //NON-NLS
+ NotifyDescriptor descriptor = new NotifyDescriptor.Message(
+ NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE);
+ DialogDisplayer.getDefault().notify(descriptor);
}
} else {
- JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.noCaseOpen"));
+ NotifyDescriptor descriptor = new NotifyDescriptor.Message(
+ NbBundle.getMessage(this.getClass(), "OpenOutputFolder.error1", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE);
+ DialogDisplayer.getDefault().notify(descriptor);
}
- } catch (IOException ex) {
- logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder"), ex); //NON-NLS
+ } catch (IllegalStateException ex) {
+ logger.log(Level.SEVERE, "OpenOutputFolderAction enabled with no current case", ex); //NON-NLS
+ JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "OpenOutputFolder.noCaseOpen"));
}
}
@@ -81,8 +89,9 @@ public final class OpenOutputFolderAction extends CallableSystemAction {
@Override
public boolean asynchronous() {
- return false; // run on edt
+ return false;
}
+
@Override
public String getName() {
return NbBundle.getMessage(OpenOutputFolderAction.class, "CTL_OpenOutputFolder");
diff --git a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java
index 1fa497dc2c..20bdd9753a 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/ShowIngestProgressSnapshotAction.java
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
- *
- * Copyright 2014 Basis Technology Corp.
+ *
+ * Copyright 2011-2017 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.
@@ -29,23 +29,24 @@ import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog;
-@ActionID(
- category = "Help",
- id = "org.sleuthkit.autopsy.actions.ShowIngestProgressSnapshotAction"
-)
-@ActionRegistration(
- displayName = "#CTL_ShowIngestProgressSnapshotAction",
- lazy = false
-)
+/**
+ * The action associated with the Help/Get Ingest Progress Snapshot menu item.
+ * It opens a the Ingest Progress Snapshot dialog.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
+ */
+@ActionID(category = "Help", id = "org.sleuthkit.autopsy.actions.ShowIngestProgressSnapshotAction")
+@ActionRegistration(displayName = "#CTL_ShowIngestProgressSnapshotAction", lazy = false)
@ActionReference(path = "Menu/Help", position = 1125)
@Messages("CTL_ShowIngestProgressSnapshotAction=Ingest Status Details")
public final class ShowIngestProgressSnapshotAction extends CallableSystemAction implements ActionListener {
private static final String ACTION_NAME = NbBundle.getMessage(ShowIngestProgressSnapshotAction.class, "ShowIngestProgressSnapshotAction.actionName.text");
+ private static final long serialVersionUID = 1L;
@Override
public void performAction() {
- IngestProgressSnapshotDialog dialog = new IngestProgressSnapshotDialog();
+ new IngestProgressSnapshotDialog();
}
@Override
@@ -65,6 +66,6 @@ public final class ShowIngestProgressSnapshotAction extends CallableSystemAction
@Override
public boolean asynchronous() {
- return false; // run on edt
+ return false;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java
index f6a102338e..1ae20d1163 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
+import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
@@ -28,7 +29,6 @@ import java.util.logging.Level;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
-import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
@@ -44,26 +44,26 @@ import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.Presenter;
import org.openide.util.lookup.ServiceProvider;
+import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.IngestRunningCheck;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.Image;
/**
- * The action to add an image to the current Case. This action should be
- * disabled on creation and it will be enabled on new case creation or case
- * opened.
+ * An action that invokes the Add Data Source wizard.
*
- * @author jantonius
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
-// TODO: need annotation because there's a "Lookup.getDefault().lookup(AddImageAction.class)"
-// used in AddImageWizardPanel1 (among other places). It really shouldn't be done like that.
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.AddImageAction")
@ActionRegistration(displayName = "#CTL_AddImage", lazy = false)
-@ActionReferences(value = {
- @ActionReference(path = "Toolbars/Case", position = 100)})
+@ActionReferences(value = {@ActionReference(path = "Toolbars/Case", position = 100)})
@ServiceProvider(service = AddImageAction.class)
public final class AddImageAction extends CallableSystemAction implements Presenter.Toolbar {
+ private static final long serialVersionUID = 1L;
+ private static final Dimension SIZE = new Dimension(875, 550);
+ private final ChangeSupport cleanupSupport = new ChangeSupport(this);
+
// Keys into the WizardDescriptor properties that pass information between stages of the wizard
// :
// String: time zone that the image is from
@@ -84,15 +84,13 @@ public final class AddImageAction extends CallableSystemAction implements Presen
static final String NOFATORPHANS_PROP = "nofatorphans"; //NON-NLS
static final Logger logger = Logger.getLogger(AddImageAction.class.getName());
- static final Dimension SIZE = new Dimension(875, 550);
-
private WizardDescriptor wizardDescriptor;
private WizardDescriptor.Iterator iterator;
private Dialog dialog;
- private JButton toolbarButton = new JButton();
+ private final JButton toolbarButton = new JButton();
/**
- * The constructor for AddImageAction class
+ * Constructs an action that invokes the Add Data Source wizard.
*/
public AddImageAction() {
putValue(Action.NAME, NbBundle.getMessage(AddImageAction.class, "CTL_AddImage")); // set the action Name
@@ -106,44 +104,39 @@ public final class AddImageAction extends CallableSystemAction implements Presen
}
});
- this.setEnabled(false); // disable this action class
+ /*
+ * Disable this action until a case is opened. Currently, the Case class
+ * enables the action.
+ */
+ this.setEnabled(false);
}
- /**
- * Pop-up the "Add Image" wizard panel.
- *
- * @param e
- */
@Override
public void actionPerformed(ActionEvent e) {
- if (IngestManager.getInstance().isIngestRunning()) {
- final String msg = NbBundle.getMessage(this.getClass(), "AddImageAction.ingestConfig.ongoingIngest.msg");
- if (JOptionPane.showConfirmDialog(null, msg,
- NbBundle.getMessage(this.getClass(),
- "AddImageAction.ingestConfig.ongoingIngest.title"),
- JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) {
- return;
+ String optionsDlgTitle = NbBundle.getMessage(this.getClass(), "AddImageAction.ingestConfig.ongoingIngest.title");
+ String optionsDlgMessage = NbBundle.getMessage(this.getClass(), "AddImageAction.ingestConfig.ongoingIngest.msg");
+ if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ iterator = new AddImageWizardIterator(this);
+ wizardDescriptor = new WizardDescriptor(iterator);
+ wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "AddImageAction.wizard.title"));
+ wizardDescriptor.putProperty(NAME, e);
+ wizardDescriptor.setTitleFormat(new MessageFormat("{0}"));
+
+ if (dialog != null) {
+ dialog.setVisible(false); // hide the old one
}
+ dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor);
+ Dimension d = dialog.getSize();
+ dialog.setSize(SIZE);
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ dialog.setVisible(true);
+ dialog.toFront();
+
+ // Do any cleanup that needs to happen (potentially: stopping the
+ //add-image process, reverting an image)
+ runCleanupTasks();
}
-
- iterator = new AddImageWizardIterator(this);
- wizardDescriptor = new WizardDescriptor(iterator);
- wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "AddImageAction.wizard.title"));
- wizardDescriptor.putProperty(NAME, e);
- wizardDescriptor.setTitleFormat(new MessageFormat("{0}"));
-
- if (dialog != null) {
- dialog.setVisible(false); // hide the old one
- }
- dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor);
- Dimension d = dialog.getSize();
- dialog.setSize(SIZE);
- dialog.setVisible(true);
- dialog.toFront();
-
- // Do any cleanup that needs to happen (potentially: stopping the
- //add-image process, reverting an image)
- runCleanupTasks();
}
/**
@@ -172,12 +165,9 @@ public final class AddImageAction extends CallableSystemAction implements Presen
void runTask(Image newImage);
}
- /**
- * This method does nothing. Use the "actionPerformed(ActionEvent e)"
- * instead of this method.
- */
@Override
public void performAction() {
+ actionPerformed(null);
}
/**
@@ -191,9 +181,9 @@ public final class AddImageAction extends CallableSystemAction implements Presen
}
/**
- * Gets the HelpCtx associated with implementing object
+ * Gets the help context for this action.
*
- * @return HelpCtx or HelpCtx.DEFAULT_HELP
+ * @return The help context.
*/
@Override
public HelpCtx getHelpCtx() {
@@ -201,9 +191,9 @@ public final class AddImageAction extends CallableSystemAction implements Presen
}
/**
- * Returns the toolbar component of this action
+ * Gets the toolbar component for this action.
*
- * @return component the toolbar button
+ * @return The toolbar button
*/
@Override
public Component getToolbarPresenter() {
@@ -214,7 +204,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen
}
/**
- * Set this action to be enabled/disabled
+ * Enables and disables this action.
*
* @param value whether to enable this action or not
*/
@@ -236,8 +226,8 @@ public final class AddImageAction extends CallableSystemAction implements Presen
public void requestFocusButton(String buttonText) {
// get all buttons on this wizard panel
Object[] wizardButtons = wizardDescriptor.getOptions();
- for (int i = 0; i < wizardButtons.length; i++) {
- JButton tempButton = (JButton) wizardButtons[i];
+ for (Object wizardButton : wizardButtons) {
+ JButton tempButton = (JButton) wizardButton;
if (tempButton.getText().equals(buttonText)) {
tempButton.setDefaultCapable(true);
tempButton.requestFocus();
@@ -254,8 +244,6 @@ public final class AddImageAction extends CallableSystemAction implements Presen
cleanupSupport.fireChange();
}
- ChangeSupport cleanupSupport = new ChangeSupport(this);
-
/**
* Instances of this class implement the cleanup() method to run cleanup
* code when the wizard exits.
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java
index cf7db4a392..0e650a5f60 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java
@@ -26,6 +26,8 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter;
+import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitJNI;
@@ -207,7 +209,8 @@ class AddImageTask implements Runnable {
errorMessages.add(verificationError);
}
if(! imageWriterPath.isEmpty()){
- Case.getCurrentCase().scheduleImageWriterFinish(imageId);
+ ImageWriter writer = new ImageWriter(imageId);
+ writer.subscribeToEvents();
}
newDataSources.add(newImage);
} else {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java
index 7204664d2f..8b1a4dd1e0 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,6 +30,7 @@ import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel;
/**
* The final panel of the add image wizard. It displays a progress bar and
@@ -39,7 +40,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
* {@link AddImageWizardIngestConfigPanel} (which is a bit weird if you ask m
* -jm)
*/
-class AddImageWizardAddingProgressPanel implements WizardDescriptor.FinishablePanel {
+class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel {
/**
* flag to indicate that the image adding process is finished and this panel
@@ -53,7 +54,7 @@ class AddImageWizardAddingProgressPanel implements WizardDescriptor.FinishablePa
private AddImageWizardAddingProgressVisual component;
private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
- private DSPProgressMonitorImpl dspProgressMonitorImpl = new DSPProgressMonitorImpl();
+ private final DSPProgressMonitorImpl dspProgressMonitorImpl = new DSPProgressMonitorImpl();
public DSPProgressMonitorImpl getDSPProgressMonitorImpl() {
return dspProgressMonitorImpl;
@@ -192,7 +193,7 @@ class AddImageWizardAddingProgressPanel implements WizardDescriptor.FinishablePa
protected final void fireChangeEvent() {
Iterator it;
synchronized (listeners) {
- it = new HashSet(listeners).iterator();
+ it = new HashSet<>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
@@ -239,8 +240,4 @@ class AddImageWizardAddingProgressPanel implements WizardDescriptor.FinishablePa
getComponent().showErrors(errorString, critical);
}
- @Override
- public boolean isFinishPanel() {
- return true;
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java
index 88762ba96a..3d58017ecb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,18 +33,19 @@ import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.windows.WindowManager;
import java.awt.Cursor;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel;
/**
* The "Add Image" wizard panel1 handling the logic of selecting image file(s)
* to add to Case, and pick the time zone.
*/
-class AddImageWizardChooseDataSourcePanel implements WizardDescriptor.Panel, PropertyChangeListener {
+class AddImageWizardChooseDataSourcePanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener {
/**
* The visual component that displays this panel. If you need to access the
* component from this class, just use getComponent().
*/
- private AddImageWizardAddingProgressPanel progressPanel;
+ private final AddImageWizardAddingProgressPanel progressPanel;
private AddImageWizardChooseDataSourceVisual component;
private boolean isNextEnable = false;
private static final String PROP_LASTDATASOURCE_PATH = "LBL_LastDataSource_PATH"; //NON-NLS
@@ -122,7 +123,7 @@ class AddImageWizardChooseDataSourcePanel implements WizardDescriptor.Panel listeners = new HashSet(1); // or can use ChangeSupport in NB 6.0
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
/**
* Adds a listener to changes of the panel's validity.
@@ -155,7 +156,7 @@ class AddImageWizardChooseDataSourcePanel implements WizardDescriptor.Panel it;
synchronized (listeners) {
- it = new HashSet(listeners).iterator();
+ it = new HashSet<>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
@@ -218,8 +219,6 @@ class AddImageWizardChooseDataSourcePanel implements WizardDescriptor.Panel sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,13 +32,17 @@ import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import org.openide.WizardDescriptor;
import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
+import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestJobSettingsPanel;
import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.IngestProfileSelectionWizardPanel;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel;
/**
* second panel of add image wizard, allows user to configure ingest modules.
@@ -46,20 +50,19 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
* TODO: review this for dead code. think about moving logic of adding image to
* 3rd panel( {@link AddImageWizardAddingProgressPanel}) separate class -jm
*/
-class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel {
-
- private final IngestJobSettingsPanel ingestJobSettingsPanel;
+class AddImageWizardIngestConfigPanel extends ShortcutWizardDescriptorPanel {
+ @Messages("AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules")
+ private IngestJobSettingsPanel ingestJobSettingsPanel;
/**
* The visual component that displays this panel. If you need to access the
* component from this class, just use getComponent().
*/
private Component component = null;
-
+ private String lastProfileUsed = AddImageWizardIngestConfigPanel.class.getCanonicalName();
private final List newContents = Collections.synchronizedList(new ArrayList());
private boolean ingested = false;
private boolean readyToIngest = false;
-
// task that will clean up the created database file if the wizard is cancelled before it finishes
private AddImageAction.CleanupTask cleanupTask;
@@ -75,10 +78,12 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,6 +26,9 @@ import javax.swing.JComponent;
import javax.swing.event.ChangeListener;
import org.openide.WizardDescriptor;
import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.ingest.IngestProfiles;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.IngestProfileSelectionWizardPanel;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel;
/**
* The iterator class for the "Add Image" wizard panel. This class is used to
@@ -34,8 +37,10 @@ import org.openide.util.NbBundle;
class AddImageWizardIterator implements WizardDescriptor.Iterator {
private int index = 0;
- private List> panels;
- private AddImageAction action;
+ private List panels;
+ private final AddImageAction action;
+ private int progressPanelIndex;
+ private final static String PROP_LASTPROFILE_NAME = "AIW_LASTPROFILE_NAME"; //NON-NLS
AddImageWizardIterator(AddImageAction action) {
this.action = action;
@@ -45,19 +50,22 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator> getPanels() {
+ private List getPanels() {
if (panels == null) {
- panels = new ArrayList>();
+ panels = new ArrayList<>();
AddImageWizardAddingProgressPanel progressPanel = new AddImageWizardAddingProgressPanel();
AddImageWizardChooseDataSourcePanel dsPanel = new AddImageWizardChooseDataSourcePanel(progressPanel);
AddImageWizardIngestConfigPanel ingestConfigPanel = new AddImageWizardIngestConfigPanel(dsPanel, action, progressPanel);
-
panels.add(dsPanel);
+ List profiles = IngestProfiles.getIngestProfiles();
+ if (!profiles.isEmpty()) {
+ panels.add(new IngestProfileSelectionWizardPanel(AddImageWizardIngestConfigPanel.class.getCanonicalName(), getPropLastprofileName()));
+ }
panels.add(ingestConfigPanel);
panels.add(progressPanel);
-
+ progressPanelIndex = panels.indexOf(progressPanel); //Doing programatically because number of panels is variable
String[] steps = new String[panels.size()];
for (int i = 0; i < panels.size(); i++) {
Component c = panels.get(i).getComponent();
@@ -66,7 +74,7 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator current() {
+ public ShortcutWizardDescriptorPanel current() {
if (panels != null) {
return panels.get(index);
} else {
@@ -146,7 +171,14 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +18,20 @@
*/
package org.sleuthkit.autopsy.casemodule;
-import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter;
-import java.awt.Cursor;
import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
@@ -34,24 +39,28 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.MissingResourceException;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.GuardedBy;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
+import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.OpenOutputFolderAction;
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
@@ -63,6 +72,8 @@ 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.coordinationservice.CoordinationService;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.core.UserPreferences;
@@ -79,27 +90,90 @@ import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
+import org.sleuthkit.autopsy.framework.AutopsyService;
+import org.sleuthkit.autopsy.framework.AutopsyService.CaseContext;
+import org.sleuthkit.autopsy.framework.LoggingProgressIndicator;
+import org.sleuthkit.autopsy.framework.ModalDialogProgressIndicator;
+import org.sleuthkit.autopsy.framework.ProgressIndicator;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
+import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.datamodel.TskException;
/**
* An Autopsy case. Currently, only one case at a time may be open.
*/
-public class Case implements SleuthkitCase.ErrorObserver {
+public class Case {
+
+ private static final int NAME_LOCK_TIMOUT_HOURS = 12;
+ private static final int SHARED_DIR_LOCK_TIMOUT_HOURS = 12;
+ private static final int RESOURCE_LOCK_TIMOUT_HOURS = 12;
+ private static final int MAX_SANITIZED_CASE_NAME_LEN = 47;
+ private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
+ private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
+ private static final String CACHE_FOLDER = "Cache"; //NON-NLS
+ private static final String EXPORT_FOLDER = "Export"; //NON-NLS
+ private static final String LOG_FOLDER = "Log"; //NON-NLS
+ private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
+ private static final String TEMP_FOLDER = "Temp"; //NON-NLS
+ private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60;
+ private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
+ private static final Logger logger = Logger.getLogger(Case.class.getName());
+ private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
+
+ /*
+ * The following fields are the mutable state associated with the current
+ * case concept. The currentCase field is guarded for writes by the
+ * currentCaseWriteLock. The field is also volatile to allow non-locking
+ * reads via the isCaseOpen and getCurrentCase methods. This is unfortunate,
+ * but Case clients that do not respond correctly to CURRENT_CASE closed
+ * events may call these methods and that would be a source of potential
+ * deadlock if the currentCaseWriteLock was used to guard read access.
+ *
+ * TODO (JIRA-2228): Throw CaseClosedException from Case instance methods.
+ */
+ private static final Object currentCaseWriteLock = new Object();
+ @GuardedBy("currentCaseWriteLock")
+ private static volatile Case currentCase;
+
+ /*
+ * The application name, used to make the title of the main application
+ * window [application name] when there is no open case and [curent case
+ * display name] - [application name] when there is an open case.
+ * Initialized by getting the main window title before a case has been
+ * opened. A reference to the main window frame is obtained at the same time
+ * as a convenmient side effect for parenting dialogs.
+ *
+ * TODO (JIRA-2231): Make the application name a RuntimeProperties item.
+ */
+ @GuardedBy("currentCaseWriteLock")
+ private static Frame mainFrame;
+ @GuardedBy("currentCaseWriteLock")
+ private static String appName;
+
+ /*
+ * Case instance data.
+ */
+ private CaseMetadata caseMetadata;
+ private CoordinationService.Lock caseDirLock;
+ private ExecutorService caseLockingExecutor;
+ private SleuthkitCase caseDb;
+ private SleuthkitErrorReporter sleuthkitErrorReporter;
+ private CollaborationMonitor collaborationMonitor;
+ private Services caseServices;
+ private boolean hasDataSources;
/**
* An enumeration of case types.
*/
- @NbBundle.Messages({"Case_caseType_singleUser=Single-user case", "Case_caseType_multiUser=Multi-user case"})
public enum CaseType {
SINGLE_USER_CASE("Single-user case"), //NON-NLS
@@ -108,12 +182,21 @@ public class Case implements SleuthkitCase.ErrorObserver {
private final String typeName;
/**
- * Constructs a case type.
+ * Gets a case type from a case type name string.
*
- * @param typeName The type name.
+ * @param typeName The case type name string.
+ *
+ * @return
*/
- private CaseType(String typeName) {
- this.typeName = typeName;
+ public static CaseType fromString(String typeName) {
+ if (typeName != null) {
+ for (CaseType c : CaseType.values()) {
+ if (typeName.equalsIgnoreCase(c.toString())) {
+ return c;
+ }
+ }
+ }
+ return null;
}
/**
@@ -129,8 +212,12 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Gets the localized display name for this case type.
*
- * @return The dis[play name.
+ * @return The display name.
*/
+ @Messages({
+ "Case_caseType_singleUser=Single-user case",
+ "Case_caseType_multiUser=Multi-user case"
+ })
String getLocalizedDisplayName() {
if (fromString(typeName) == SINGLE_USER_CASE) {
return Bundle.Case_caseType_singleUser();
@@ -140,30 +227,21 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Gets a case type from a case type name string
+ * Constructs a case type.
*
- * @param typeName The case type name string.
- *
- * @return
+ * @param typeName The type name.
*/
- public static CaseType fromString(String typeName) {
- if (typeName != null) {
- for (CaseType c : CaseType.values()) {
- if (typeName.equalsIgnoreCase(c.typeName)) {
- return c;
- }
- }
- }
- return null;
+ private CaseType(String typeName) {
+ this.typeName = typeName;
}
/**
- * Tests the equality of the type name of this case type with a case
- * type name.
+ * Tests the equality of the type name of this case type with another
+ * case type name.
*
- * @param otherTypeName A case type name
+ * @param otherTypeName A case type name,
*
- * @return True or false
+ * @return True or false,
*
* @deprecated Do not use.
*/
@@ -175,8 +253,8 @@ public class Case implements SleuthkitCase.ErrorObserver {
};
/**
- * An enumeration of events (property change events) a case may publish
- * (fire).
+ * An enumeration of the case events (property change events) a case may
+ * publish (fire).
*/
public enum Events {
@@ -188,8 +266,8 @@ public class Case implements SleuthkitCase.ErrorObserver {
NAME,
/**
* The number of the current case has changed. The old value of the
- * PropertyChangeEvent is the old number (type: String), the new value
- * is the new case number (type: String).
+ * PropertyChangeEvent is the old case number (type: String), the new
+ * value is the new case number (type: String).
*/
NUMBER,
/**
@@ -203,7 +281,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* made. The old and new values of the PropertyChangeEvent are null.
* Cast the PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent to
- * access event data.
+ * access additional event data.
*/
ADDING_DATA_SOURCE,
/**
@@ -211,7 +289,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* The old and new values of the PropertyChangeEvent are null. Cast the
* PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent
- * to access event data.
+ * to access additional event data.
*/
ADDING_DATA_SOURCE_FAILED,
/**
@@ -219,7 +297,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* of the PropertyChangeEvent is null, the new value is the newly-added
* data source (type: Content). Cast the PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent to
- * access event data.
+ * access additional event data.
*/
DATA_SOURCE_ADDED,
/**
@@ -229,11 +307,23 @@ public class Case implements SleuthkitCase.ErrorObserver {
*/
DATA_SOURCE_DELETED,
/**
- * The current case has changed. If a case has been opened, the old
- * value of the PropertyChangeEvent is null, the new value is the new
- * case (type: Case). If a case has been closed, the old value of the
- * PropertyChangeEvent is the closed case (type: Case), the new value is
- * null.
+ * The current case has changed.
+ *
+ * If a new case has been opened as the current case, the old value of
+ * the PropertyChangeEvent is null, and the new value is the new case
+ * (type: Case).
+ *
+ * If the current case has been closed, the old value of the
+ * PropertyChangeEvent is the closed case (type: Case), and the new
+ * value is null. IMPORTANT: Subscribers to this event should not call
+ * isCaseOpen or getCurrentCase in the interval between a case closed
+ * event and a case opened event. If there is any need for upon closing
+ * interaction with a closed case, the case in the old value should be
+ * used, and it should be done synchronously in the CURRENT_CASE event
+ * handler.
+ *
+ * TODO (JIRA-2228): Throw CaseClosedException from Case instance
+ * methods.
*/
CURRENT_CASE,
/**
@@ -243,7 +333,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
*/
REPORT_ADDED,
/**
- * A report has been added to the current case. The old value of the
+ * A report has been deleted from the current case. The old value of the
* PropertyChangeEvent is the report (type: Report), the new value is
* null.
*/
@@ -256,8 +346,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
BLACKBOARD_ARTIFACT_TAG_ADDED,
/**
* A tag has been removed from an artifact associated with the current
- * case. The old value of the PropertyChangeEvent is is the tag info
- * (type:
+ * case. The old value of the PropertyChangeEvent is the tag info (type:
* BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo),
* the new value is null.
*/
@@ -276,39 +365,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
CONTENT_TAG_DELETED;
};
- private static final int MAX_SANITIZED_CASE_NAME_LEN = 47;
- private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
- private static final String CACHE_FOLDER = "Cache"; //NON-NLS
- private static final String EXPORT_FOLDER = "Export"; //NON-NLS
- private static final String LOG_FOLDER = "Log"; //NON-NLS
- private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
- private static final String TEMP_FOLDER = "Temp"; //NON-NLS
- static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
- private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60;
- private static final Logger logger = Logger.getLogger(Case.class.getName());
- private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
- private final ImageWriter imageWriter = new ImageWriter();
- private static String appName;
- private static Case currentCase;
- private static CoordinationService.Lock currentCaseLock;
- private static ExecutorService currentCaseExecutor;
- private final CaseMetadata caseMetadata;
- private final SleuthkitCase db;
- private final Services services;
- private CollaborationMonitor collaborationMonitor;
- private boolean hasDataSources;
- private volatile IntervalErrorReportData tskErrorReporter;
-
- /**
- * Constructs an Autopsy case. Currently, only one case at a time may be
- * open.
- */
- private Case(CaseMetadata caseMetadata, SleuthkitCase db) {
- this.caseMetadata = caseMetadata;
- this.db = db;
- this.services = new Services(db);
- }
-
/**
* Adds a subscriber to all case events. To subscribe to only specific
* events, use one of the overloads of addEventSubscriber.
@@ -374,7 +430,116 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Checks if case is currently open.
+ * Checks if a case display name is valid, i.e., does not include any
+ * characters that cannot be used in file names.
+ *
+ * @param caseName The candidate case name.
+ *
+ * @return True or false.
+ */
+ public static boolean isValidName(String caseName) {
+ return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
+ || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
+ || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
+ }
+
+ /**
+ * Creates a new case and makes it the current case.
+ *
+ * IMPORTANT: This method should not be called in the event dispatch thread
+ * (EDT).
+ *
+ * @param caseDir The full path of the case directory. The directory
+ * will be created if it doesn't already exist; if it
+ * exists, it is ASSUMED it was created by calling
+ * createCaseDirectory.
+ * @param caseDisplayName The display name of case, which may be changed
+ * later by the user.
+ * @param caseNumber The case number, can be the empty string.
+ * @param examiner The examiner to associate with the case, can be
+ * the empty string.
+ * @param caseType The type of case (single-user or multi-user).
+ *
+ * @throws CaseActionException if there is a problem creating the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ @Messages({
+ "# {0} - exception message", "Case.exceptionMessage.wrapperMessage={0}"
+ })
+ public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
+ synchronized (currentCaseWriteLock) {
+ if (RuntimeProperties.runningWithGUI()) {
+ getMainWindowAndAppName();
+ }
+
+ if (null != currentCase) {
+ try {
+ closeCurrentCase();
+ } catch (CaseActionException ex) {
+ logger.log(Level.SEVERE, "Error closing the previous current case", ex); //NON-NLS
+ }
+ }
+
+ logger.log(Level.INFO, "Creating current case with display name {0} in {1}", new Object[]{caseDisplayName, caseDir}); //NON-NLS
+ Case newCurrentCase = new Case();
+ newCurrentCase.open(caseDir, caseDisplayName, caseNumber, examiner, caseType);
+ currentCase = newCurrentCase;
+ logger.log(Level.INFO, "Created currrent case {0} (display name {1}) in {2}", new Object[]{newCurrentCase.getName(), caseDisplayName, caseDir}); //NON-NLS
+ if (RuntimeProperties.runningWithGUI()) {
+ updateGUIForCaseOpened(newCurrentCase);
+ }
+ eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, newCurrentCase));
+ }
+ }
+
+ /**
+ * Opens an existing case and makes it the current case.
+ *
+ * IMPORTANT: This method should not be called in the event dispatch thread
+ * (EDT).
+ *
+ * @param caseMetadataFilePath The path of the case metadata (.aut) file.
+ *
+ * @throws CaseActionException if there is a problem opening the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ @Messages({
+ "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}",
+ "Case.progressIndicatorTitle.openingCase=Opening Case",
+ "Case.exceptionMessage.failedToReadMetadata=Failed to read metadata."
+ })
+ public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
+ synchronized (currentCaseWriteLock) {
+ if (RuntimeProperties.runningWithGUI()) {
+ getMainWindowAndAppName();
+ }
+
+ if (null != currentCase) {
+ try {
+ closeCurrentCase();
+ } catch (CaseActionException ex) {
+ logger.log(Level.SEVERE, "Error closing the previous current case", ex);
+ }
+ }
+
+ Case newCurrentCase = new Case();
+ logger.log(Level.INFO, "Opening case with metadata file path {0} as current case", caseMetadataFilePath); //NON-NLS
+ newCurrentCase.open(Paths.get(caseMetadataFilePath));
+ currentCase = newCurrentCase;
+ logger.log(Level.INFO, "Opened case with metadata file path {0} as current case", caseMetadataFilePath); //NON-NLS
+ if (RuntimeProperties.runningWithGUI()) {
+ updateGUIForCaseOpened(newCurrentCase);
+ }
+ eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
+ }
+ }
+
+ /**
+ * Checks if a case, the current case, is open at the time it is called.
*
* @return True or false.
*/
@@ -383,27 +548,616 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Gets the current case, if there is one.
+ * Gets the current case, if there is one, at the time of the call.
*
* @return The current case.
*
* @throws IllegalStateException if there is no current case.
*/
public static Case getCurrentCase() {
- if (currentCase != null) {
- return currentCase;
+ Case caseToReturn = currentCase;
+ if (null != caseToReturn) {
+ return caseToReturn;
} else {
throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
}
}
+ /**
+ * Closes the current case if there is a current case.
+ *
+ * IMPORTANT: This method should not be called in the event dispatch thread
+ * (EDT).
+ * @throws CaseActionException
+ */
+ @Messages({
+ "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
+ "Case.progressIndicatorTitle.closingCase=Closing Case"
+ })
+ public static void closeCurrentCase() throws CaseActionException {
+ synchronized (currentCaseWriteLock) {
+ if (null == currentCase) {
+ return;
+ }
+ String caseName = currentCase.getName();
+ String caseDir = currentCase.getCaseDirectory();
+ try {
+ Case closedCase = currentCase;
+ eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
+ logger.log(Level.INFO, "Closing current case {0} in {1}", new Object[]{caseName, caseDir}); //NON-NLS
+ currentCase = null;
+ closedCase.close();
+ } finally {
+ /*
+ * The case is no longer the current case, even if an exception
+ * was thrown.
+ */
+ logger.log(Level.INFO, "Closed current case {0} in {1}", new Object[]{caseName, caseDir}); //NON-NLS
+ if (RuntimeProperties.runningWithGUI()) {
+ updateGUIForCaseClosed();
+ }
+ }
+ }
+ }
+
+ /**
+ * Deletes the current case.
+ *
+ * IMPORTANT: This method should not be called in the event dispatch thread
+ * (EDT).
+ *
+ * @throws CaseActionException if there is a problem deleting the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ public static void deleteCurrentCase() throws CaseActionException {
+ synchronized (currentCaseWriteLock) {
+ if (null == currentCase) {
+ return;
+ }
+ CaseMetadata metadata = currentCase.getCaseMetadata();
+ closeCurrentCase();
+ deleteCase(metadata);
+ }
+ }
+
+ /**
+ * Deletes a case. This method cannot be used to delete the current case;
+ * deleting the current case must be done by calling Case.deleteCurrentCase.
+ *
+ * @param metadata The metadata for the case to delete.
+ *
+ * @throws CaseActionException if there is a problem deleting the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ @Messages({
+ "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}",
+ "Case.progressIndicatorTitle.deletingCase=Deleting Case",
+ "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first",
+ "Case.progressMessage.deletingTextIndex=Deleting text index...",
+ "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
+ "Case.exceptionMessage.cancelled=Cancelled by user"
+ })
+ public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
+ synchronized (currentCaseWriteLock) {
+ if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(metadata.getCaseDirectory())) {
+ throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()));
+ }
+ }
+
+ /*
+ * Set up either a GUI progress indicator or a logging progress
+ * indicator.
+ */
+ ProgressIndicator progressIndicator;
+ if (RuntimeProperties.runningWithGUI()) {
+ progressIndicator = new ModalDialogProgressIndicator(
+ mainFrame,
+ Bundle.Case_progressIndicatorTitle_deletingCase());
+ } else {
+ progressIndicator = new LoggingProgressIndicator();
+ }
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+
+ logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ Future future = executor.submit(() -> {
+ if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
+ cleanupDeletedCase(metadata, progressIndicator);
+ } else {
+ /*
+ * First, acquire an exclusive case directory lock. The case
+ * cannot be deleted if another node has it open.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks());
+ try (CoordinationService.Lock dirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory())) {
+ assert (null != dirLock);
+
+ /*
+ * Delete the text index.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_deletingTextIndex());
+
+ for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
+ )) {
+ searchService.deleteTextIndex(metadata.getTextIndexName());
+ }
+
+ if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
+ /*
+ * Delete the case database from the database server.
+ * The case database for a single-user case is in the
+ * case directory and will be deleted whe it is deleted.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase());
+ CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
+ Class.forName("org.postgresql.Driver"); //NON-NLS
+ try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
+ Statement statement = connection.createStatement();) {
+ String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
+ statement.execute(deleteCommand);
+ }
+ }
+
+ cleanupDeletedCase(metadata, progressIndicator);
+ }
+ }
+ return null;
+ });
+
+ try {
+ future.get();
+ logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
+ } catch (InterruptedException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
+ } catch (ExecutionException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
+ } catch (CancellationException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ /**
+ * Sanitizes the case name for use as a PostgreSQL database name and in
+ * ActiveMQ event channel (topic) names.
+ *
+ * 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
+ *
+ * ActiveMQ:
+ * http://activemq.2283324.n4.nabble.com/What-are-limitations-restrictions-on-destination-name-td4664141.html
+ * may not be ?
+ *
+ * @param caseName A candidate case name.
+ *
+ * @return The sanitized case name.
+ *
+ * @throws org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException
+ */
+ static String sanitizeCaseName(String caseName) throws IllegalCaseNameException {
+
+ String result;
+
+ // Remove all non-ASCII characters
+ result = caseName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
+
+ // Remove all control characters
+ result = result.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
+
+ // Remove / \ : ? space ' "
+ result = result.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
+
+ // 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_CASE_NAME_LEN) {
+ result = result.substring(0, MAX_SANITIZED_CASE_NAME_LEN);
+ }
+
+ if (result.isEmpty()) {
+ throw new IllegalCaseNameException(String.format("Failed to sanitize case name '%s'", caseName));
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a case directory and its subdirectories.
+ *
+ * @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, CaseType caseType) throws CaseActionException {
+
+ File caseDirF = new File(caseDir);
+
+ if (caseDirF.exists()) {
+ if (caseDirF.isFile()) {
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
+
+ } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
+ }
+ }
+
+ try {
+ boolean result = (caseDirF).mkdirs(); // create root case Directory
+
+ if (result == false) {
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
+ }
+
+ // create the folders inside the case directory
+ 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 + hostClause + File.separator + MODULE_FOLDER;
+ result = new File(modulesOutDir).mkdir();
+
+ if (result == false) {
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
+ 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 (MissingResourceException | CaseActionException e) {
+ throw new CaseActionException(
+ NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
+ }
+ }
+
+ /**
+ * Gets the paths of data sources that are images.
+ *
+ * @param db A case database.
+ *
+ * @return A mapping of object ids to image paths.
+ */
+ static Map getImagePaths(SleuthkitCase db) {
+ Map imgPaths = new HashMap<>();
+ try {
+ Map> imgPathsList = db.getImagePaths();
+ for (Map.Entry> entry : imgPathsList.entrySet()) {
+ if (entry.getValue().size() > 0) {
+ imgPaths.put(entry.getKey(), entry.getValue().get(0));
+ }
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
+ }
+ return imgPaths;
+ }
+
+ /**
+ * Gets a reference to the main window of the desktop application to use to
+ * parent pop ups and initializes the application name for use in changing
+ * the main window title. MUST be called BEFORE any case is opened or
+ * created.
+ *
+ * @throws CaseActionException
+ */
+ @Messages({
+ "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
+ })
+ private static void getMainWindowAndAppName() throws CaseActionException {
+ if (RuntimeProperties.runningWithGUI() && null == mainFrame) {
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ mainFrame = WindowManager.getDefault().getMainWindow();
+ /*
+ * This is tricky and fragile. What looks like lazy
+ * initialization of the appName field is actually getting
+ * the application name from the main window title BEFORE a
+ * case has been opened and a case name has been included in
+ * the title. It is also very specific to the desktop GUI.
+ *
+ * TODO (JIRA-2231): Make the application name a
+ * RuntimeProperties item set by Installers.
+ */
+ appName = mainFrame.getTitle();
+ });
+ } catch (InterruptedException | InvocationTargetException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(Bundle.Case_exceptionMessage_cannotLocateMainWindow()), ex);
+ }
+ }
+ }
+
+ /**
+ * Deletes the case directory of a deleted case and removes the case form
+ * the Recent Cases menu.
+ *
+ * @param metadata The case metadata.
+ * @param progressIndicator A progress indicator.
+ */
+ @Messages({
+ "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
+ })
+ private static void cleanupDeletedCase(CaseMetadata metadata, ProgressIndicator progressIndicator) {
+ /*
+ * Delete the case directory.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDirectory());
+ if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
+ logger.log(Level.SEVERE, "Failed to fully delete case directory {0}", metadata.getCaseDirectory());
+ }
+
+ /*
+ * If running in a GUI, remove the case from the Recent Cases menu
+ */
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> {
+ RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
+ });
+ }
+ }
+
+ /**
+ * Acquires an exclusive case name lock.
+ *
+ * @param caseName The case name (not the case display name, which can be
+ * changed by a user).
+ *
+ * @return The lock.
+ *
+ * @throws CaseActionException with a user-friendly message if the lock
+ * cannot be acquired.
+ */
+ @Messages({"Case.creationException.couldNotAcquireNameLock=Failed to get lock on case name"})
+ private static CoordinationService.Lock acquireExclusiveCaseNameLock(String caseName) throws CaseActionException {
+ try {
+ Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, NAME_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
+ if (null == lock) {
+ throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock());
+ }
+ return lock;
+
+ } catch (InterruptedException | CoordinationServiceException ex) {
+ throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex);
+ }
+ }
+
+ /**
+ * Acquires an exclusive case resources lock.
+ *
+ * @param caseName The case name (not the case display name, which can be
+ * changed by a user).
+ *
+ * @return The lock.
+ *
+ * @throws CaseActionException with a user-friendly message if the lock
+ * cannot be acquired.
+ */
+ @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
+ private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseName) throws CaseActionException {
+ try {
+ String resourcesNodeName = caseName + "_resources";
+ Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, resourcesNodeName, RESOURCE_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
+ if (null == lock) {
+ throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
+ }
+ return lock;
+
+ } catch (InterruptedException | CoordinationServiceException ex) {
+ throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
+ }
+ }
+
+ /**
+ * Update the GUI to to reflect the current case.
+ */
+ private static void updateGUIForCaseOpened(Case newCurrentCase) {
+ SwingUtilities.invokeLater(() -> {
+ /*
+ * If the case database was upgraded for a new schema and a backup
+ * database was created, notify the user.
+ */
+ SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
+ String backupDbPath = caseDb.getBackupDatabasePath();
+ if (null != backupDbPath) {
+ JOptionPane.showMessageDialog(
+ WindowManager.getDefault().getMainWindow(),
+ NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
+ NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ /*
+ * Look for the files for the data sources listed in the case
+ * database and give the user the opportunity to locate any that are
+ * missing.
+ */
+ Map imgPaths = getImagePaths(caseDb);
+ for (Map.Entry entry : imgPaths.entrySet()) {
+ long obj_id = entry.getKey();
+ String path = entry.getValue();
+ boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
+
+ if (!fileExists) {
+ int response = JOptionPane.showConfirmDialog(
+ WindowManager.getDefault().getMainWindow(),
+ NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", appName, path),
+ NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
+ JOptionPane.YES_NO_OPTION);
+ if (response == JOptionPane.YES_OPTION) {
+ MissingImageDialog.makeDialog(obj_id, caseDb);
+ } else {
+ logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
+
+ }
+ }
+ }
+
+ /*
+ * Enable the case-specific actions.
+ */
+ CallableSystemAction.get(AddImageAction.class
+ ).setEnabled(true);
+ CallableSystemAction
+ .get(CaseCloseAction.class
+ ).setEnabled(true);
+ CallableSystemAction
+ .get(CasePropertiesAction.class
+ ).setEnabled(true);
+ CallableSystemAction
+ .get(CaseDeleteAction.class
+ ).setEnabled(true);
+ CallableSystemAction
+ .get(OpenTimelineAction.class
+ ).setEnabled(true);
+ CallableSystemAction
+ .get(OpenOutputFolderAction.class
+ ).setEnabled(false);
+
+ /*
+ * Add the case to the recent cases tracker that supplies a list of
+ * recent cases to the recent cases menu item and the open/create
+ * case dialog.
+ */
+ RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getCaseMetadata().getFilePath().toString());
+
+ /*
+ * Open the top components (windows within the main application
+ * window).
+ */
+ if (newCurrentCase.hasData()) {
+ CoreComponentControl.openCoreWindows();
+ }
+
+ /*
+ * Reset the main window title to be [curent case display name] -
+ * [application name], instead of just the application name.
+ */
+ addCaseNameToMainWindowTitle(newCurrentCase.getDisplayName());
+ });
+ }
+
+ /*
+ * Update the GUI to to reflect the lack of a current case.
+ */
+ private static void updateGUIForCaseClosed() {
+ SwingUtilities.invokeLater(() -> {
+
+ /*
+ * Close the top components (windows within the main application
+ * window).
+ */
+ CoreComponentControl.closeCoreWindows();
+
+ /*
+ * Disable the case-specific menu items.
+ */
+ CallableSystemAction
+ .get(AddImageAction.class
+ ).setEnabled(false);
+ CallableSystemAction
+ .get(CaseCloseAction.class
+ ).setEnabled(false);
+ CallableSystemAction
+ .get(CasePropertiesAction.class
+ ).setEnabled(false);
+ CallableSystemAction
+ .get(CaseDeleteAction.class
+ ).setEnabled(false);
+ CallableSystemAction
+ .get(OpenTimelineAction.class
+ ).setEnabled(false);
+ CallableSystemAction
+ .get(OpenOutputFolderAction.class
+ ).setEnabled(false);
+
+ /*
+ * Clear the notifications in the notfier component in the lower
+ * right hand corner of the main application window.
+ */
+ MessageNotifyUtil.Notify.clear();
+
+ /*
+ * Reset the main window title to be just the application name,
+ * instead of [curent case display name] - [application name].
+ */
+ Frame mainWindow = WindowManager.getDefault().getMainWindow();
+ mainWindow.setTitle(appName);
+ });
+ }
+
+ /**
+ * Changes the title of the main window to include the case name.
+ *
+ * @param caseName The name of the case.
+ */
+ private static void addCaseNameToMainWindowTitle(String caseName) {
+ if (!caseName.isEmpty()) {
+ Frame frame = WindowManager.getDefault().getMainWindow();
+ frame.setTitle(caseName + " - " + appName);
+ }
+ }
+
+ /**
+ * Empties the temp subdirectory for the current case.
+ */
+ private static void clearTempSubDir(String tempSubDirPath) {
+ File tempFolder = new File(tempSubDirPath);
+ if (tempFolder.isDirectory()) {
+ File[] files = tempFolder.listFiles();
+ if (files.length > 0) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ FileUtil.deleteDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ }
+ }
+ }
+
/**
* Gets the case database.
*
* @return The case database.
*/
public SleuthkitCase getSleuthkitCase() {
- return this.db;
+ return this.caseDb;
}
/**
@@ -412,16 +1166,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @return The case services manager.
*/
public Services getServices() {
- return services;
- }
-
- /**
- * Gets the case metadata.
- *
- * @return A CaseMetaData object.
- */
- CaseMetadata getCaseMetadata() {
- return caseMetadata;
+ return caseServices;
}
/**
@@ -443,7 +1188,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Gets the case name.
+ * Gets the immutable case name.
*
* @return The case name.
*/
@@ -452,30 +1197,12 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Updates the case name.
+ * Gets the case name that can be changed by the user.
*
- * This should not be called from the EDT.
- *
- * @param oldCaseName The old case name.
- * @param oldPath The old path name.
- * @param newCaseName The new case name.
- * @param newPath The new case path.
+ * @return The case display name.
*/
- void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
- try {
- caseMetadata.setCaseName(newCaseName);
- eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName));
- SwingUtilities.invokeLater(() -> {
- try {
- RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case
- addCaseNameToMainWindowTitle(newCaseName);
- } catch (Exception ex) {
- Logger.getLogger(Case.class.getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
- }
- });
- } catch (Exception ex) {
- throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex);
- }
+ public String getDisplayName() {
+ return getCaseMetadata().getCaseDisplayName();
}
/**
@@ -603,20 +1330,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
}
- /**
- * Gets the path to the specified subdirectory of the case directory,
- * creating it if it does not already exist.
- *
- * @return The absolute path to the specified subdirectory.
- */
- private String getOrCreateSubdirectory(String subDirectoryName) {
- File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
- if (!subDirectory.exists()) {
- subDirectory.mkdirs();
- }
- return subDirectory.toString();
- }
-
/**
* Gets the data sources for the case.
*
@@ -627,7 +1340,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* database.
*/
public List getDataSources() throws TskCoreException {
- List list = db.getRootObjects();
+ List list = caseDb.getRootObjects();
hasDataSources = (list.size() > 0);
return list;
}
@@ -653,6 +1366,17 @@ public class Case implements SleuthkitCase.ErrorObserver {
return timezones;
}
+ /**
+ * Sets the name of the keyword search index for the case.
+ *
+ * @param textIndexName The text index name.
+ *
+ * @throws CaseMetadataException
+ */
+ public void setTextIndexName(String textIndexName) throws CaseMetadataException {
+ getCaseMetadata().setTextIndexName(textIndexName);
+ }
+
/**
* Gets the name of the keyword search index for the case.
*
@@ -785,7 +1509,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
throw new TskCoreException(errorMsg, ex);
}
- Report report = this.db.addReport(normalizedLocalPath, srcModuleName, reportName);
+ Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName);
eventPublisher.publish(new ReportAddedEvent(report));
}
@@ -798,7 +1522,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* database.
*/
public List getAllReports() throws TskCoreException {
- return this.db.getAllReports();
+ return this.caseDb.getAllReports();
}
/**
@@ -811,30 +1535,968 @@ public class Case implements SleuthkitCase.ErrorObserver {
*/
public void deleteReports(Collection extends Report> reports) throws TskCoreException {
for (Report report : reports) {
- this.db.deleteReport(report);
+ this.caseDb.deleteReport(report);
eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
}
}
/**
- * Allows the case database to report internal error conditions in
- * situations where throwing an exception is not appropriate.
+ * Gets the case metadata.
*
- * @param context The context of the error condition.
- * @param errorMessage An error message.
+ * @return A CaseMetaData object.
*/
- @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);
+ CaseMetadata getCaseMetadata() {
+ return caseMetadata;
+ }
+
+ /**
+ * Updates the case display name name.
+ *
+ * @param oldCaseName The old case name.
+ * @param oldPath The old path name.
+ * @param newCaseName The new case name.
+ * @param newPath The new case path.
+ */
+ void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
+ try {
+ caseMetadata.setCaseDisplayName(newCaseName);
+ eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName));
+ SwingUtilities.invokeLater(() -> {
+ try {
+ RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath);
+ addCaseNameToMainWindowTitle(newCaseName);
+
+ } catch (Exception ex) {
+ Logger.getLogger(Case.class
+ .getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
+ }
+ });
+ } catch (CaseMetadataException ex) {
+ throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex);
}
}
+ /**
+ * Constructs an Autopsy case.
+ */
+ private Case() {
+ }
+
+ /**
+ * @param caseDir The full path of the case directory. The directory
+ * will be created if it doesn't already exist; if it
+ * exists, it is ASSUMED it was created by calling
+ * createCaseDirectory.
+ * @param caseDisplayName The display name of case, which may be changed
+ * later by the user.
+ * @param caseNumber The case number, can be the empty string.
+ * @param examiner The examiner to associate with the case, can be
+ * the empty string.
+ * @param caseType The type of case (single-user or multi-user).
+ *
+ * @throws CaseActionException if there is a problem creating the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ @Messages({
+ "Case.exceptionMessage.illegalCaseName=Case name contains illegal characters.",
+ "Case.progressIndicatorTitle.creatingCase=Creating Case",
+ "Case.progressIndicatorCancelButton.label=Cancel",
+ "Case.progressMessage.preparing=Preparing...",
+ "Case.progressMessage.acquiringLocks=Preparing to open case resources. This may take time if another user is upgrading the case."
+ })
+ private void open(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
+ /*
+ * Clean up the display name for the case to make a suitable immutable
+ * case name.
+ */
+ String caseName;
+ try {
+ caseName = sanitizeCaseName(caseDisplayName);
+ } catch (IllegalCaseNameException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(Bundle.Case_exceptionMessage_illegalCaseName()), ex);
+ }
+
+ /*
+ * Set up either a GUI progress indicator or a logging progress
+ * indicator.
+ */
+ final CancelButtonListener listener = new CancelButtonListener();
+ ProgressIndicator progressIndicator;
+ if (RuntimeProperties.runningWithGUI()) {
+ progressIndicator = new ModalDialogProgressIndicator(
+ mainFrame,
+ Bundle.Case_progressIndicatorTitle_creatingCase(),
+ new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
+ Bundle.Case_progressIndicatorCancelButton_label(),
+ listener);
+ } else {
+ progressIndicator = new LoggingProgressIndicator();
+ }
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+
+ /*
+ * Creating a case is always done in the same non-UI thread that will be
+ * used later to close the case. If the case is a multi-user case, this
+ * ensures that case directory lock that is held as long as the case is
+ * open is released in the same thread in which it was acquired, as is
+ * required by the coordination service.
+ */
+ caseLockingExecutor = Executors.newSingleThreadExecutor();
+ Future future = caseLockingExecutor.submit(() -> {
+ if (CaseType.SINGLE_USER_CASE == caseType) {
+ open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator);
+ } else {
+ /*
+ * First, acquire an exclusive case name lock to prevent two
+ * nodes from creating the same case at the same time.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks());
+ try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) {
+ assert (null != nameLock);
+ /*
+ * Next, acquire a shared case directory lock that will be
+ * held as long as this node has this case open. This will
+ * prevent deletion of the case by another node.
+ */
+ acquireSharedCaseDirLock(caseDir);
+ /*
+ * Finally, acquire an exclusive case resources lock to
+ * ensure only one node at a time can
+ * create/open/upgrade/close the case resources.
+ */
+ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseName)) {
+ assert (null != resourcesLock);
+ try {
+ open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator);
+ } catch (CaseActionException ex) {
+ /*
+ * Release the case directory lock immediately if
+ * there was a problem opening the case.
+ */
+ if (CaseType.MULTI_USER_CASE == caseType) {
+ releaseSharedCaseDirLock(caseName);
+ }
+ throw ex;
+ }
+ }
+ }
+ }
+ return null;
+ });
+
+ /*
+ * If running with a GUI, give the future for the case creation task to
+ * the cancel button listener for the GUI progress indicator and make
+ * the progress indicator visible to the user.
+ */
+ if (RuntimeProperties.runningWithGUI()) {
+ listener.setCaseActionFuture(future);
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
+ }
+
+ /*
+ * Wait for the case creation task to finish.
+ */
+ try {
+ future.get();
+
+ } catch (InterruptedException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
+ } catch (ExecutionException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
+ } catch (CancellationException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
+ } finally {
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
+ }
+ }
+ }
+
+ /**
+ * Creates and opens a new case.
+ *
+ * @param caseDir The full path of the case directory. The directory
+ * will be created if it doesn't already exist; if it
+ * exists, it is ASSUMED it was created by calling
+ * createCaseDirectory.
+ * @param caseDisplayName The display name of case, which may be changed
+ * later by the user.
+ * @param caseNumber The case number, can be the empty string.
+ * @param examiner The examiner to associate with the case, can be
+ * the empty string.
+ * @param caseType The type of case (single-user or multi-user).
+ *
+ * @throws CaseActionException if there is a problem creating the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ @Messages({
+ "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
+ "Case.progressMessage.creatingCaseDatabase=Creating case database...",
+ "Case.progressMessage.creatingCaseMetadataFile=Creating case metadata file...",
+ "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file.",
+ "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database."
+ })
+ private void open(String caseDir, String caseName, String caseDisplayName, String caseNumber, String examiner, CaseType caseType, ProgressIndicator progressIndicator) throws CaseActionException {
+ /*
+ * Create the case directory, if it does not already exist.
+ *
+ * TODO (JIRA-2180): The reason for this check for the existence of the
+ * case directory is not at all obvious. It reflects the assumption that
+ * if the case directory already exists, it is because the case is being
+ * created using the the "New Case" wizard, which separates the creation
+ * of the case directory from the creation of the case, with the idea
+ * that if the case directory cannot be created, the user can be asked
+ * to supply a different case directory path. This of course creates
+ * subtle and undetectable coupling between this code and the wizard
+ * code. The desired effect could be accomplished more simply and safely
+ * by having this method throw a specific exception to indicate that the
+ * case directory could not be created. In fact, a FEW specific
+ * exception types would in turn allow us to put localized,
+ * user-friendly messages in the GUI instead of putting user-friendly,
+ * localized messages in the exceptions, which causes them to appear in
+ * the application log, where it would be better to use English for
+ * readability by the broadest group of developers.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
+ if (new File(caseDir).exists() == false) {
+ Case.createCaseDirectory(caseDir, caseType);
+ }
+
+ /*
+ * Create the case database.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
+ String dbName = null;
+ try {
+ if (CaseType.SINGLE_USER_CASE == caseType) {
+ /*
+ * For single-user cases, the case database is a SQLite database
+ * with a fixed name and is physically located in the root of
+ * the case directory.
+ */
+ dbName = SINGLE_USER_CASE_DB_NAME;
+ this.caseDb = SleuthkitCase.newCase(Paths.get(caseDir, SINGLE_USER_CASE_DB_NAME).toString());
+ } else if (CaseType.MULTI_USER_CASE == caseType) {
+ /*
+ * For multi-user cases, the case database is a PostgreSQL
+ * database with a name consiting of the case name with a time
+ * stamp suffix and is physically located on the database
+ * server.
+ */
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
+ Date date = new Date();
+ dbName = caseName + "_" + dateFormat.format(date);
+ this.caseDb = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir);
+ }
+ } catch (TskCoreException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(), ex);
+
+ } catch (UserPreferencesException ex) {
+ throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
+ }
+
+ /*
+ * Create the case metadata (.aut) file.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseMetadataFile());
+ try {
+ this.caseMetadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName);
+ } catch (CaseMetadataException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
+ }
+
+ openServices(progressIndicator);
+ }
+
+ /**
+ * Opens an existing case.
+ *
+ * @param caseMetadataFilePath The apth to the case metadata file.
+ *
+ * @throws CaseActionException if there is a problem creating the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ private void open(Path caseMetadataFilePath) throws CaseActionException {
+ /*
+ * Read the contents of the case metadata file.
+ */
+ try {
+ caseMetadata = new CaseMetadata(caseMetadataFilePath);
+ } catch (CaseMetadataException ex) {
+ throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex);
+ }
+
+ /*
+ * Set up either a GUI progress indicator or a logging progress
+ * indicator.
+ */
+ CancelButtonListener listener = new CancelButtonListener();
+ ProgressIndicator progressIndicator;
+ if (RuntimeProperties.runningWithGUI()) {
+ progressIndicator = new ModalDialogProgressIndicator(
+ mainFrame,
+ Bundle.Case_progressIndicatorTitle_openingCase(),
+ new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
+ Bundle.Case_progressIndicatorCancelButton_label(),
+ listener);
+ } else {
+ progressIndicator = new LoggingProgressIndicator();
+ }
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+
+ /*
+ * Opening the case in the same thread that will be used later to close
+ * the case. If the case is a multi-user case, this ensures that case
+ * directory lock that is held as long as the case is open is released
+ * in the same thread in which it was acquired, as is required by the
+ * coordination service.
+ */
+ CaseType caseType = caseMetadata.getCaseType();
+ String caseName = caseMetadata.getCaseName();
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+ caseLockingExecutor = Executors.newSingleThreadExecutor();
+ Future future = caseLockingExecutor.submit(() -> {
+ if (CaseType.SINGLE_USER_CASE == caseType) {
+ openCaseDatabase(progressIndicator);
+ openServices(progressIndicator);
+ } else {
+ /*
+ * First, acquire a shared case directory lock that will be held
+ * as long as this node has this case open, in order to prevent
+ * deletion of the case by another node.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks());
+ acquireSharedCaseDirLock(caseMetadata.getCaseDirectory());
+ /*
+ * Next, acquire an exclusive case resources lock to ensure only
+ * one node at a time can create/open/upgrade/close case
+ * resources.
+ */
+ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseName())) {
+ assert (null != resourcesLock);
+ try {
+ openCaseDatabase(progressIndicator);
+ openServices(progressIndicator);
+ } catch (CaseActionException ex) {
+ /*
+ * Release the case directory lock immediately if there
+ * was a problem opening the case.
+ */
+ if (CaseType.MULTI_USER_CASE == caseType) {
+ releaseSharedCaseDirLock(caseName);
+ }
+ throw ex;
+ }
+ }
+ }
+ return null;
+ });
+
+ /*
+ * If running with a GUI, give the future for the case opening task to
+ * the cancel button listener for the GUI progress indicator and make
+ * the progress indicator visible to the user.
+ */
+ if (RuntimeProperties.runningWithGUI()) {
+ listener.setCaseActionFuture(future);
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
+ }
+
+ /*
+ * Wait for the case opening task to finish.
+ */
+ try {
+ future.get();
+
+ } catch (InterruptedException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
+ } catch (ExecutionException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
+ } catch (CancellationException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
+ } finally {
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
+ }
+ }
+ }
+
+ /**
+ * Opens an existing case database.
+ *
+ * @param progressIndicator A progress indicator.
+ *
+ * @throws CaseActionException if there is a problem opening the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ */
+ @Messages({
+ "Case.progressMessage.openingCaseDatabase=Opening case database...",
+ "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database."
+ })
+ private void openCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
+ /*
+ * Open the case database.
+ */
+ try {
+ progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
+ if (CaseType.SINGLE_USER_CASE == caseMetadata.getCaseType()) {
+ this.caseDb = SleuthkitCase.openCase(Paths.get(caseMetadata.getCaseDirectory(), caseMetadata.getCaseDatabaseName()).toString());
+ } else if (UserPreferences.getIsMultiUserModeEnabled()) {
+ try {
+ this.caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
+
+ } catch (UserPreferencesException ex) {
+ throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
+
+ }
+ } else {
+ throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
+ }
+ } catch (TskCoreException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
+ }
+ }
+
+ /**
+ * Completes the case opening tasks common to both new cases and existing
+ * cases.
+ *
+ * @param progressIndicator A progress indicator.
+ *
+ * @throws CaseActionException
+ */
+ @Messages({
+ "Case.progressMessage.switchingLogDirectory=Switching log directory...",
+ "Case.progressMessage.settingUpTskErrorReporting=Setting up SleuthKit error reporting...",
+ "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
+ "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
+ "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
+ private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
+ /*
+ * Switch to writing to the application logs in the logs subdirectory.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
+ Logger.setLogDirectory(getLogDirectoryPath());
+
+ /*
+ * Hook up a SleuthKit layer error reporter.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_settingUpTskErrorReporting());
+
+ this.sleuthkitErrorReporter = new SleuthkitErrorReporter(MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
+ this.caseDb.addErrorObserver(this.sleuthkitErrorReporter);
+
+ /*
+ * Clear the temp subdirectory.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
+ Case.clearTempSubDir(this.getTempDirectory());
+
+ /*
+ * Open the case-level services.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
+ this.caseServices = new Services(this.caseDb);
+
+ /*
+ * Allow any registered application services to open any resources
+ * specific to this case.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
+ openAppServiceCaseResources();
+
+ /*
+ * If this case is a multi-user case, set up for communication with
+ * other nodes.
+ */
+ if (CaseType.MULTI_USER_CASE == this.caseMetadata.getCaseType()) {
+ progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
+ try {
+ eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, this.getName()));
+ collaborationMonitor = new CollaborationMonitor(this.getName());
+ } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
+ /*
+ * The collaboration monitor and event channel are not
+ * essential. Log an error and notify the user, but do not
+ * throw.
+ */
+ logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
+
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
+ }
+ }
+ }
+ }
+
+ /**
+ * Allows any registered application-level services to open any resources
+ * specific to this case.
+ *
+ * @throws CaseActionException
+ */
+ @NbBundle.Messages({
+ "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
+ "# {0} - service name", "Case.servicesException.notificationTitle={0} Error",
+ "# {0} - service name", "Case.servicesException.serviceResourcesOpenCancelled=Opening case resources for {0} cancelled",
+ "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesOpenError=Could not open case resources for {0} service: {1}"
+ })
+ private void openAppServiceCaseResources() throws CaseActionException {
+ /*
+ * Each service gets its own independently cancellable task, and thus
+ * its own task progress indicator.
+ */
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
+ CancelButtonListener buttonListener = new CancelButtonListener();
+ ProgressIndicator progressIndicator;
+ if (RuntimeProperties.runningWithGUI()) {
+ progressIndicator = new ModalDialogProgressIndicator(
+ mainFrame,
+ Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
+ new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
+ Bundle.Case_progressIndicatorCancelButton_label(),
+ buttonListener);
+ } else {
+ progressIndicator = new LoggingProgressIndicator();
+ }
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+
+ AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
+ if (RuntimeProperties.runningWithGUI()) {
+ buttonListener.setCaseContext(context);
+ }
+ Future future = executor.submit(() -> {
+ service.openCaseResources(context);
+ return null;
+ });
+ if (RuntimeProperties.runningWithGUI()) {
+ buttonListener.setCaseActionFuture(future);
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
+ }
+ try {
+ future.get();
+ } catch (InterruptedException ex) {
+ Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to open case resources", service.getServiceName()), ex);
+
+ } catch (CancellationException ex) {
+ /*
+ * The case-specific application service resources are not
+ * essential. Log an error and notify the user if running the
+ * desktop GUI, but do not throw.
+ */
+ Case.logger.log(Level.WARNING, String.format("%s service opening of case resources cancelled", service.getServiceName()));
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.warn(
+ Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
+ Bundle.Case_servicesException_serviceResourcesOpenCancelled(service.getServiceName())));
+ }
+ } catch (ExecutionException ex) {
+ /*
+ * The case-specific application service resources are not
+ * essential. Log an error and notify the user if running the
+ * desktop GUI, but do not throw.
+ */
+ Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
+ Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
+ Bundle.Case_servicesException_serviceResourcesOpenError(service.getServiceName(), ex.getLocalizedMessage())));
+ }
+ } finally {
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
+ }
+ }
+ }
+ /*
+ * No tasks left, simply shut down the executor.
+ */
+ executor.shutdown();
+ }
+
+ /**
+ * Closes the case.
+ *
+ * @param progressIndicator A progress indicator.
+ */
+ @Messages({
+ "Case.progressMessage.notifyingCaseEventSubscribers=Notifying case event subscribers...",
+ "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
+ "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
+ "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
+ "Case.progressMessage.tearingDownNetworkCommunications=Tearing down network communications...",
+ "Case.progressMessage.closingCaseDatabase=Closing case database...",
+ "Case.progressMessage.tearingDownTskErrorReporting=Tearing down SleuthKit error reporting..."
+ })
+
+ private void close() throws CaseActionException {
+ /*
+ * Set up either a GUI progress indicator or a logging progress
+ * indicator.
+ */
+ ProgressIndicator progressIndicator;
+ if (RuntimeProperties.runningWithGUI()) {
+ progressIndicator = new ModalDialogProgressIndicator(
+ Case.mainFrame,
+ Bundle.Case_progressIndicatorTitle_closingCase());
+ } else {
+ progressIndicator = new LoggingProgressIndicator();
+ }
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+
+ /*
+ * Closing a case is always done in the same non-UI thread that
+ * opened/created the case. If the case is a multi-user case, this
+ * ensures that case directory lock that is held as long as the case is
+ * open is released in the same thread in which it was acquired, as is
+ * required by the coordination service.
+ */
+ Future future = caseLockingExecutor.submit(() -> {
+ if (CaseType.SINGLE_USER_CASE == caseMetadata.getCaseType()) {
+ close(progressIndicator);
+ } else {
+ String caseName = caseMetadata.getCaseName();
+ /*
+ * Acquire an exclusive case resources lock to ensure only one
+ * node at a time can create/open/upgrade/close the case
+ * resources.
+ */
+ progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks());
+ try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseName())) {
+ assert (null != resourcesLock);
+ close(progressIndicator);
+ } finally {
+ /*
+ * Always release the case directory lock that was acquired
+ * when the case was opened.
+ */
+ releaseSharedCaseDirLock(caseName);
+ }
+ }
+ return null;
+ });
+
+ /*
+ * If running with a GUI, give the future for the case closing task to
+ * the cancel button listener for the GUI progress indicator and make
+ * the progress indicator visible to the user.
+ */
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
+ }
+
+ try {
+ logger.log(Level.INFO, "Closing case with metadata file path {0}", getCaseMetadata().getFilePath()); //NON-NLS
+ future.get();
+ } catch (InterruptedException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
+ } catch (ExecutionException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
+ } catch (CancellationException ex) {
+ throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
+ } finally {
+ caseLockingExecutor.shutdown();
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
+ }
+ }
+ }
+
+ private void close(ProgressIndicator progressIndicator) {
+ IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED);
+
+ /*
+ * Stop sending/receiving case events to and from other nodes if this is
+ * a multi-user case.
+ */
+ if (CaseType.MULTI_USER_CASE == caseMetadata.getCaseType()) {
+ progressIndicator.progress(Bundle.Case_progressMessage_tearingDownNetworkCommunications());
+ if (null != collaborationMonitor) {
+ collaborationMonitor.shutdown();
+ }
+ eventPublisher.closeRemoteEventChannel();
+ }
+
+ /*
+ * Allow all registered application services providers to close
+ * resources related to the case.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
+ closeAppServiceCaseResources();
+
+ /*
+ * Close the case-level services.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
+ try {
+ this.caseServices.close();
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
+ }
+
+ /*
+ * Close the case database
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
+ caseDb.close();
+
+ /*
+ * Disconnect the SleuthKit layer error reporter.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_tearingDownTskErrorReporting());
+ caseDb.removeErrorObserver(sleuthkitErrorReporter);
+
+ /*
+ * Switch the log directory.
+ */
+ progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
+ Logger.setLogDirectory(PlatformUtil.getLogDirectory());
+ }
+
+ /**
+ * Allows any registered application-level services to close any resources
+ * specific to this case.
+ */
+ @Messages({
+ "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
+ "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
+ })
+ private void closeAppServiceCaseResources() {
+ /*
+ * Each service gets its own independently cancellable task, and thus
+ * its own task progress indicator.
+ */
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
+ )) {
+ ProgressIndicator progressIndicator;
+ if (RuntimeProperties.runningWithGUI()) {
+ progressIndicator = new ModalDialogProgressIndicator(
+ mainFrame,
+ Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
+ } else {
+ progressIndicator = new LoggingProgressIndicator();
+ }
+ progressIndicator.start(Bundle.Case_progressMessage_preparing());
+
+ AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
+ Future future = executor.submit(() -> {
+ service.closeCaseResources(context);
+ return null;
+ });
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
+ }
+ try {
+ future.get();
+ } catch (InterruptedException ex) {
+ Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
+
+ } catch (CancellationException ex) {
+ Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
+
+ } catch (ExecutionException ex) {
+ Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
+ Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
+ Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
+ }
+ } finally {
+ if (RuntimeProperties.runningWithGUI()) {
+ SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
+ }
+ }
+ }
+ /*
+ * No tasks left, simply shut down the executor.
+ */
+ executor.shutdown();
+ }
+
+ /**
+ * Acquires a shared case directory lock for the current case.
+ *
+ * @param caseDir The full path of the case directory.
+ *
+ * @throws CaseActionException with a user-friendly message if the lock
+ * cannot be acquired.
+ */
+ @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
+ private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
+ try {
+ caseDirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir, SHARED_DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
+ if (null == caseDirLock) {
+ throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
+ }
+ } catch (InterruptedException | CoordinationServiceException ex) {
+ throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireNameLock(), ex);
+ }
+ }
+
+ /**
+ * Releases a shared case directory lock for the current case.
+ *
+ * @param caseDir The full path of the case directory.
+ */
+ private void releaseSharedCaseDirLock(String caseDir) {
+ if (caseDirLock != null) {
+ try {
+ caseDirLock.release();
+ caseDirLock = null;
+ } catch (CoordinationService.CoordinationServiceException ex) {
+ logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
+ }
+ }
+ }
+
+ /**
+ * Gets the path to the specified subdirectory of the case directory,
+ * creating it if it does not already exist.
+ *
+ * @return The absolute path to the specified subdirectory.
+ */
+ private String getOrCreateSubdirectory(String subDirectoryName) {
+ File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
+ if (!subDirectory.exists()) {
+ subDirectory.mkdirs();
+ }
+ return subDirectory.toString();
+
+ }
+
+ private final static class CancelButtonListener implements ActionListener {
+
+ private Future> caseActionFuture;
+ private CaseContext caseContext;
+
+ private void setCaseActionFuture(Future> caseActionFuture) {
+ this.caseActionFuture = caseActionFuture;
+ }
+
+ private void setCaseContext(CaseContext caseContext) {
+ this.caseContext = caseContext;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (null != this.caseContext) {
+ this.caseContext.requestCancel();
+ }
+ if (null != this.caseActionFuture) {
+ this.caseActionFuture.cancel(true);
+ }
+ }
+
+ }
+
+ /**
+ * An exception to throw when a case name with invalid characters is
+ * encountered.
+ */
+ final static class IllegalCaseNameException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an exception to throw when a case name with invalid
+ * characters is encountered.
+ *
+ * @param message The exception message.
+ */
+ IllegalCaseNameException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception to throw when a case name with invalid
+ * characters is encountered.
+ *
+ * @param message The exception message.
+ * @param cause The exceptin cause.
+ */
+ IllegalCaseNameException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Creates a new, single-user Autopsy case.
+ *
+ * @param caseDir The full path of the case directory. The directory
+ * will be created if it doesn't already exist; if it
+ * exists, it is ASSUMED it was created by calling
+ * createCaseDirectory.
+ * @param caseDisplayName The display name of case, which may be changed
+ * later by the user.
+ * @param caseNumber The case number, can be the empty string.
+ * @param examiner The examiner to associate with the case, can be
+ * the empty string.
+ *
+ * @throws CaseActionException if there is a problem creating the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ * @deprecated Use createAsCurrentCase instead.
+ */
+ @Deprecated
+ public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
+ createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
+ }
+
+ /**
+ * Creates a new Autopsy case and makes it the current case.
+ *
+ * @param caseDir The full path of the case directory. The directory
+ * will be created if it doesn't already exist; if it
+ * exists, it is ASSUMED it was created by calling
+ * createCaseDirectory.
+ * @param caseDisplayName The display name of case, which may be changed
+ * later by the user.
+ * @param caseNumber The case number, can be the empty string.
+ * @param examiner The examiner to associate with the case, can be
+ * the empty string.
+ * @param caseType The type of case (single-user or multi-user).
+ *
+ * @throws CaseActionException if there is a problem creating the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ * @deprecated Use createAsCurrentCase instead.
+ */
+ @Deprecated
+ public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
+ createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
+ }
+
+ /**
+ * Opens an existing Autopsy case and makes it the current case.
+ *
+ * @param caseMetadataFilePath The path of the case metadata (.aut) file.
+ *
+ * @throws CaseActionException if there is a problem opening the case. The
+ * exception will have a user-friendly message
+ * and may be a wrapper for a lower-level
+ * exception.
+ * @deprecated Use openAsCurrentCase instead.
+ */
+ @Deprecated
+ public static void open(String caseMetadataFilePath) throws CaseActionException {
+ openAsCurrentCase(caseMetadataFilePath);
+ }
+
/**
* Closes this Autopsy case.
*
@@ -842,851 +2504,11 @@ public class Case implements SleuthkitCase.ErrorObserver {
* exception will have a user-friendly message
* and may be a wrapper for a lower-level
* exception.
+ * @deprecated Use closeCurrentCase instead.
*/
+ @Deprecated
public void closeCase() throws CaseActionException {
-
- // The unlock must happen on the same thread that created the lock
- Future future = getCurrentCaseExecutor().submit(() -> {
- try{
- if (currentCaseLock != null) {
- currentCaseLock.release();
- currentCaseLock = null;
- }
- } catch (CoordinationService.CoordinationServiceException exx) {
- logger.log(Level.SEVERE, String.format("Error releasing shared lock"), exx);
- }
- return null;
- });
- try{
- future.get();
- } catch (InterruptedException | ExecutionException ex){
- logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex);
- }
-
- changeCurrentCase(null);
-
- try {
- services.close();
- this.db.close();
- } catch (Exception e) {
- throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.closeCase.exception.msg"), e);
- }
- }
-
- /**
- * Deletes the case folder for this Autopsy case and sets the current case
- * to null. It does not not delete the case database for a multi-user case.
- *
- * @param caseDir The case directory to delete.
- *
- * @throws CaseActionException exception throw if case could not be deleted
- */
- void deleteCase(File caseDir) throws CaseActionException {
- logger.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir); //NON-NLS
-
- try {
- boolean result = deleteCaseDirectory(caseDir);
-
- RecentCases.getInstance().removeRecentCase(this.caseMetadata.getCaseName(), this.caseMetadata.getFilePath().toString()); // remove it from the recent case
- Case.changeCurrentCase(null);
- if (result == false) {
- throw new CaseActionException(
- NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg", caseDir));
- }
-
- } catch (Exception ex) {
- logger.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex); //NON-NLS
- throw new CaseActionException(
- NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg2", caseDir), ex);
- }
- }
-
- /**
- * Gets the application name.
- *
- * @return The application name.
- */
- public static String getAppName() {
- if ((appName == null) || appName.equals("")) {
- appName = WindowManager.getDefault().getMainWindow().getTitle();
- }
- return appName;
- }
-
- /**
- * Checks if a string is a valid case name.
- *
- * TODO( AUT-2221): This should incorporate the vlaidity checks of
- * sanitizeCaseName.
- *
- * @param caseName The candidate string.
- *
- * @return True or false.
- */
- public static boolean isValidName(String caseName) {
- return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
- || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
- || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
- }
-
- /**
- * Creates a new single-user Autopsy case.
- *
- * @param caseDir The full path of the case directory. It will be created
- * if it doesn't already exist; if it exists, it should
- * have been created using Case.createCaseDirectory to
- * ensure that the required sub-directories were created.
- * @param caseName The name of case.
- * @param caseNumber The case number, can be the empty string.
- * @param examiner The examiner to associate with the case, can be the
- * empty string.
- *
- * @throws CaseActionException if there is a problem creating the case. The
- * exception will have a user-friendly message
- * and may be a wrapper for a lower-level
- * exception.
- */
- 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 Autopsy case.
- *
- * @param caseDir The full path of the case directory. It will be created
- * if it doesn't already exist; if it exists, it should
- * have been created using Case.createCaseDirectory() to
- * ensure that the required sub-directories were created.
- * @param caseName The name of case.
- * @param caseNumber The case number, can be the empty string.
- * @param examiner The examiner to associate with the case, can be the
- * empty string.
- * @param caseType The type of case (single-user or multi-user).
- *
- * @throws CaseActionException if there is a problem creating the case. The
- * exception will have a user-friendly message
- * and may be a wrapper for a lower-level
- * exception.
- */
- @Messages({"Case.creationException=Could not create case: failed to create case metadata file."})
- public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
- logger.log(Level.INFO, "Attempting to create case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS
-
- CoordinationService.Lock exclusiveResourceLock = null;
- boolean caseCreatedSuccessfully = false;
-
- /*
- * Create case directory if it doesn't already exist.
- */
- if (new File(caseDir).exists() == false) {
- Case.createCaseDirectory(caseDir, caseType);
- }
-
- /*
- * Sanitize the case name, create a unique keyword search index name,
- * and create a standard (single-user) or unique (multi-user) case
- * database name.
- */
- String santizedCaseName = sanitizeCaseName(caseName);
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
- Date date = new Date();
- String indexName = santizedCaseName + "_" + dateFormat.format(date);
- String dbName = null;
- if (caseType == CaseType.SINGLE_USER_CASE) {
- dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS
- } else if (caseType == CaseType.MULTI_USER_CASE) {
- dbName = indexName;
- }
-
- try{
- /* If this is a multi-user case, acquire two locks:
- * - a shared lock on the case to prevent it from being deleted while open
- * - an exclusive lock to prevent multiple clients from executing openCase simultaneously
- */
- if(caseType == CaseType.MULTI_USER_CASE){
- try{
-
- // The shared lock uses case directory
- // The shared lock needs to be created on a special thread so it can be released
- // from the same thread.
- Future future = getCurrentCaseExecutor().submit(() -> {
- currentCaseLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir);
- if (null == currentCaseLock) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension()));
- }
- return null;
- });
- future.get();
-
- // The exclusive lock uses the unique case name.
- // This lock does not need to be on a special thread since it will be released before
- // leaving this method
- exclusiveResourceLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE,
- dbName, 12, TimeUnit.HOURS);
- if (null == exclusiveResourceLock) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension()));
- }
- } catch (Exception ex){
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension()));
- }
- }
-
- /*
- * Create the case metadata (.aut) file.
- */
- CaseMetadata metadata;
- try {
- metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName);
- } catch (CaseMetadataException ex) {
- throw new CaseActionException(Bundle.Case_creationException(), ex);
- }
-
- /*
- * Create the case database.
- */
- SleuthkitCase db = null;
- try {
- 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, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- });
- /*
- * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly
- * messages, so propagate the exception message.
- */
- throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
- } catch (UserPreferencesException ex) {
- logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- });
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
- }
-
- Case newCase = new Case(metadata, db);
- changeCurrentCase(newCase);
- caseCreatedSuccessfully = true;
-
- logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS
- } finally {
- // Release the exclusive resource lock
- try {
- if (exclusiveResourceLock != null) {
- exclusiveResourceLock.release();
- }
- } catch (CoordinationService.CoordinationServiceException exx) {
- logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseName), exx);
- }
-
- // If an error occurred while opening the case, release the shared case lock as well
- if(! caseCreatedSuccessfully){
- Future future = getCurrentCaseExecutor().submit(() -> {
- try {
- if (currentCaseLock != null) {
- currentCaseLock.release();
- currentCaseLock = null;
- }
- } catch (CoordinationService.CoordinationServiceException exx) {
- logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseName), exx);
- }
- return null;
- });
- try{
- future.get();
- } catch (InterruptedException | ExecutionException ex){
- logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex);
- }
- }
- }
- }
-
- /**
- * Sanitizes 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.
- *
- * 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 and follows the Windows
- * naming convention
- *
- * @param caseName A candidate case name.
- *
- * @return The sanitized case name.
- */
- static String sanitizeCaseName(String caseName) {
-
- String result;
-
- // Remove all non-ASCII characters
- result = caseName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
-
- // Remove all control characters
- result = result.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
-
- // Remove / \ : ? space ' "
- result = result.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
-
- // 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_CASE_NAME_LEN) {
- result = result.substring(0, MAX_SANITIZED_CASE_NAME_LEN);
- }
-
- if (result.isEmpty()) {
- result = "case"; //NON-NLS
- }
-
- return result;
- }
-
- /**
- * Creates a case directory and its subdirectories.
- *
- * @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, CaseType caseType) throws CaseActionException {
-
- File caseDirF = new File(caseDir);
- if (caseDirF.exists()) {
- if (caseDirF.isFile()) {
- throw new CaseActionException(
- NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
- } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
- throw new CaseActionException(
- NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
- }
- }
-
- try {
- boolean result = (caseDirF).mkdirs(); // create root case Directory
- if (result == false) {
- throw new CaseActionException(
- NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
- }
-
- // create the folders inside the case directory
- 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 + hostClause + File.separator + MODULE_FOLDER;
- result = new File(modulesOutDir).mkdir();
- if (result == false) {
- throw new CaseActionException(
- NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
- 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);
- }
- }
-
- /**
- * Opens an existing Autopsy case.
- *
- * @param caseMetadataFilePath The path of the case metadata (.aut) file.
- *
- * @throws CaseActionException if there is a problem opening the case. The
- * exception will have a user-friendly message
- * and may be a wrapper for a lower-level
- * exception.
- */
- public static void open(String caseMetadataFilePath) throws CaseActionException {
- logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
- CoordinationService.Lock exclusiveResourceLock = null;
- boolean caseOpenedSuccessfully = false;
-
- /*
- * Verify the extension of the case metadata file.
- */
- if (!caseMetadataFilePath.endsWith(CaseMetadata.getFileExtension())) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CaseMetadata.getFileExtension()));
- }
-
- try {
- /*
- * Get the case metadata required to open the case database.
- */
- CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
- CaseType caseType = metadata.getCaseType();
-
- /*
- * If this is a multi-user case, acquire two locks:
- * - a shared lock on the case to prevent it from being deleted while open
- * - an exclusive lock to prevent multiple clients from executing openCase simultaneously
- */
- if(caseType == CaseType.MULTI_USER_CASE){
- try{
-
- // The shared lock uses the case directory
- // The shared lock needs to be created on a special thread so it can be released
- // from the same thread.
- Future future = getCurrentCaseExecutor().submit(() -> {
- currentCaseLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory());
- if (null == currentCaseLock) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension()));
- }
- return null;
- });
- future.get();
-
- // The exclusive lock uses the unique case name
- // This lock does not need to be on a special thread since it will be released before
- // leaving this method
- exclusiveResourceLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE,
- metadata.getCaseDatabaseName(), 12, TimeUnit.HOURS);
- if (null == exclusiveResourceLock) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension()));
- }
- } catch (Exception ex){
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension()));
- }
- }
-
- /*
- * Open the case database.
- */
- SleuthkitCase db;
- if (caseType == CaseType.SINGLE_USER_CASE) {
- String dbPath = metadata.getCaseDatabasePath(); //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(), metadata.getCaseDirectory());
- } catch (UserPreferencesException ex) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
- }
- }
-
- /*
- * Check for the presence of the UI and do things that can only be
- * done with user interaction.
- */
- if (RuntimeProperties.coreComponentsAreActive()) {
- /*
- * If the case database was upgraded for a new schema, notify
- * the user.
- */
- if (null != db.getBackupDatabasePath()) {
- SwingUtilities.invokeLater(() -> {
- JOptionPane.showMessageDialog(
- WindowManager.getDefault().getMainWindow(),
- NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", db.getBackupDatabasePath()),
- NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
- JOptionPane.INFORMATION_MESSAGE);
- });
- }
-
- /*
- * Look for the files for the data sources listed in the case
- * database and give the user the opportunity to locate any that
- * are missing.
- */
- Map imgPaths = getImagePaths(db);
- for (Map.Entry entry : imgPaths.entrySet()) {
- long obj_id = entry.getKey();
- String path = entry.getValue();
- boolean fileExists = (new File(path).isFile() || driveExists(path));
- if (!fileExists) {
- int ret = JOptionPane.showConfirmDialog(
- WindowManager.getDefault().getMainWindow(),
- NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path),
- NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
- JOptionPane.YES_NO_OPTION);
- if (ret == JOptionPane.YES_OPTION) {
- MissingImageDialog.makeDialog(obj_id, db);
- } else {
- logger.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS
- }
- }
- }
- }
- Case openedCase = new Case(metadata, db);
- changeCurrentCase(openedCase);
- caseOpenedSuccessfully = true;
-
- } catch (CaseMetadataException ex) {
- throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS
- } catch (TskCoreException ex) {
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- });
- /*
- * SleuthkitCase.openCase throws TskCoreExceptions with
- * user-friendly messages, so propagate the exception message.
- */
- throw new CaseActionException(ex.getMessage(), ex);
- } finally {
- // Release the exclusive resource lock
- try {
- if (exclusiveResourceLock != null) {
- exclusiveResourceLock.release();
- exclusiveResourceLock = null;
- }
- } catch (CoordinationService.CoordinationServiceException exx) {
- logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseMetadataFilePath), exx);
- }
-
- // If an error occurred while opening the case, release the shared case lock as well
- if(! caseOpenedSuccessfully){
- Future future = getCurrentCaseExecutor().submit(() -> {
- try {
- if (currentCaseLock != null) {
- currentCaseLock.release();
- currentCaseLock = null;
- }
- } catch (CoordinationService.CoordinationServiceException exx) {
- logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseMetadataFilePath), exx);
- }
- return null;
- });
- try{
- future.get();
- } catch (InterruptedException | ExecutionException ex){
- logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex);
- }
- }
- }
- }
-
- /**
- * Gets the paths of data sources that are images.
- *
- * @param db A case database.
- *
- * @return A mapping of object ids to image paths.
- */
- static Map getImagePaths(SleuthkitCase db) {
- Map imgPaths = new HashMap<>();
- try {
- Map> imgPathsList = db.getImagePaths();
- for (Map.Entry> entry : imgPathsList.entrySet()) {
- if (entry.getValue().size() > 0) {
- imgPaths.put(entry.getKey(), entry.getValue().get(0));
- }
- }
- } catch (TskException ex) {
- logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
- }
- return imgPaths;
- }
-
- /**
- * Updates the current case to the given case, firing property change events
- * and updating the UI.
- *
- * @param newCase The new current case or null if there is no new current
- * case.
- */
- private static void changeCurrentCase(Case newCase) {
- // close the existing case
- Case oldCase = Case.currentCase;
- Case.currentCase = null;
- if (oldCase != null) {
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- });
- IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED);
- oldCase.closeImageWriter();
- completeCaseChange(null); //closes windows, etc
- if (null != oldCase.tskErrorReporter) {
- oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case
- oldCase.tskErrorReporter = null;
- }
- 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());
- // sanity check
- if (null != currentCase.tskErrorReporter) {
- currentCase.tskErrorReporter.shutdown();
- }
- // start listening for TSK errors for the new case
- currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECS_BETWEEN_TSK_ERROR_REPORTS,
- NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
- completeCaseChange(currentCase);
- SwingUtilities.invokeLater(() -> {
- RecentCases.getInstance().addRecentCase(currentCase.getName(), currentCase.getCaseMetadata().getFilePath().toString()); // 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); //NON-NLS
- MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"));
- }
- }
- eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
-
- } else {
- Logger.setLogDirectory(PlatformUtil.getLogDirectory());
- }
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- });
- }
-
- /**
- * Updates the UI and does miscellaneous other things to complete changing
- * the current case.
- *
- * @param newCase The new current case or null if there is no new current
- * case.
- */
- private static void completeCaseChange(Case newCase) {
- logger.log(Level.INFO, "Changing Case to: {0}", newCase); //NON-NLS
- if (newCase != null) { // new case is open
-
- // clear the temp folder when the case is created / opened
- Case.clearTempFolder();
-
- 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
- CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
-
- if (newCase.hasData()) {
- // open all top components
- CoreComponentControl.openCoreWindows();
- } else {
- // close all top components
- CoreComponentControl.closeCoreWindows();
- }
- addCaseNameToMainWindowTitle(currentCase.getName());
- });
- } else {
- SwingUtilities.invokeLater(() -> {
- Frame f = WindowManager.getDefault().getMainWindow();
- f.setTitle(getAppName()); // set the window name to just application name
- });
- }
-
- } else { // case is closed
- SwingUtilities.invokeLater(() -> {
- if (RuntimeProperties.coreComponentsAreActive()) {
-
- // 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
- CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
- }
-
- //clear pending notifications
- MessageNotifyUtil.Notify.clear();
- Frame f = WindowManager.getDefault().getMainWindow();
- f.setTitle(getAppName()); // set the window name to just application name
- });
-
- //try to force gc to happen
- System.gc();
- System.gc();
- }
-
- //log memory usage after case changed
- logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
-
- }
-
- /**
- * Empties the temp subdirectory for the current case.
- */
- private static void clearTempFolder() {
- File tempFolder = new File(currentCase.getTempDirectory());
- if (tempFolder.isDirectory()) {
- File[] files = tempFolder.listFiles();
- if (files.length > 0) {
- for (File file : files) {
- if (file.isDirectory()) {
- deleteCaseDirectory(file);
- } else {
- file.delete();
- }
- }
- }
- }
- }
-
- /**
- * Changes the title of the main window to include the case name.
- *
- * @param newCaseName The name of the case.
- */
- private static void addCaseNameToMainWindowTitle(String newCaseName) {
- if (!newCaseName.equals("")) {
- Frame f = WindowManager.getDefault().getMainWindow();
- f.setTitle(newCaseName + " - " + getAppName());
- }
- }
-
- /**
- * Deletes a case directory.
- *
- * @param casePath A case directory path.
- *
- * @return True if the deletion succeeded, false otherwise.
- */
- static boolean deleteCaseDirectory(File casePath) {
- logger.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS
- return FileUtil.deleteDir(casePath);
- }
-
- /**
- * Get the single thread executor for the current case, creating it if necessary.
- * @return The executor
- */
- private static ExecutorService getCurrentCaseExecutor(){
- if(currentCaseExecutor == null){
- currentCaseExecutor = Executors.newSingleThreadExecutor();
- }
- return currentCaseExecutor;
- }
-
- /**
- * Register the ID of an image that is being copied using ImageWriter.
- * This will cause the image to be finished after ingest is complete.
- * @param imageID The image ID
- */
- void scheduleImageWriterFinish(long imageID){
- imageWriter.addDataSourceId(imageID);
- }
-
- /**
- * Cancel all tasks associated with Image Writer
- */
- void closeImageWriter(){
- imageWriter.close();
- }
-
- /**
- * Gets the time zone(s) of the image data source(s) in this case.
- *
- * @return The set of time zones in use.
- *
- * @deprecated Use getTimeZones instead.
- */
- @Deprecated
- public Set getTimeZone() {
- return getTimeZones();
- }
-
- /**
- * Determines whether or not a given path is for a physical drive.
- *
- * @param path The path to test.
- *
- * @return True or false.
- *
- * @deprecated Use
- * org.sleuthkit.autopsy.coreutils.DriveUtils.isPhysicalDrive instead.
- */
- @Deprecated
- static boolean isPhysicalDrive(String path) {
- return DriveUtils.isPhysicalDrive(path);
- }
-
- /**
- * Determines whether or not a given path is for a local drive or partition.
- *
- * @param path The path to test.
- *
- * @deprecated Use org.sleuthkit.autopsy.coreutils.DriveUtils.isPartition
- * instead.
- */
- @Deprecated
- static boolean isPartition(String path) {
- return DriveUtils.isPartition(path);
- }
-
- /**
- * Determines whether or not a drive exists by eading the first byte and
- * checking if it is a -1.
- *
- * @param path The path to test.
- *
- * @return True or false.
- *
- * @deprecated Use org.sleuthkit.autopsy.coreutils.DriveUtils.driveExists
- * instead.
- */
- @Deprecated
- static boolean driveExists(String path) {
- return DriveUtils.driveExists(path);
+ closeCurrentCase();
}
/**
@@ -1744,21 +2566,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
return Version.getVersion();
}
- /**
- * Creates an Autopsy case directory.
- *
- * @param caseDir Path to the case directory (typically base + case name)
- * @param caseName the case name (used only for error messages)
- *
- * @throws CaseActionException
- * @Deprecated Use createCaseDirectory(String caseDir, CaseType caseType)
- * instead
- */
- @Deprecated
- static void createCaseDirectory(String caseDir, String caseName) throws CaseActionException {
- createCaseDirectory(caseDir, CaseType.SINGLE_USER_CASE);
- }
-
/**
* Check if case is currently open.
*
@@ -1768,20 +2575,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
*/
@Deprecated
public static boolean existsCurrentCase() {
- return currentCase != null;
- }
-
- /**
- * 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();
+ return isCaseOpen();
}
/**
@@ -1808,20 +2602,23 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @deprecated Do not use.
*/
@Deprecated
- public static PropertyChangeSupport getPropertyChangeSupport() {
- return new PropertyChangeSupport(Case.class);
+ public static PropertyChangeSupport
+ getPropertyChangeSupport() {
+ return new PropertyChangeSupport(Case.class
+ );
}
/**
- * Gets the full path to the case metadata file for this case.
+ * Get module output directory path where modules should save their
+ * permanent data.
*
- * @return configFilePath The case metadata file path.
+ * @return absolute path to the module output directory
*
- * @deprecated Use getCaseMetadata and CaseMetadata.getFilePath instead.
+ * @deprecated Use getModuleDirectory() instead.
*/
@Deprecated
- String getConfigFilePath() {
- return getCaseMetadata().getFilePath().toString();
+ public String getModulesOutputDirAbsPath() {
+ return getModuleDirectory();
}
/**
@@ -1832,32 +2629,33 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @param imgId The ID of the image.
* @param timeZone The time zone of the image.
*
+ * @return
+ *
+ * @throws org.sleuthkit.autopsy.casemodule.CaseActionException
+ *
* @deprecated As of release 4.0
*/
@Deprecated
public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
try {
- Image newDataSource = db.getImageById(imgId);
+ Image newDataSource = caseDb.getImageById(imgId);
notifyDataSourceAdded(newDataSource, UUID.randomUUID());
return newDataSource;
- } catch (Exception ex) {
+ } catch (TskCoreException 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.
+ * Gets the time zone(s) of the image data source(s) in this case.
*
- * @param newDataSource new data source added
+ * @return The set of time zones in use.
*
- * @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 Use getTimeZones instead.
*/
@Deprecated
- void addLocalDataSource(Content newDataSource) {
- notifyDataSourceAdded(newDataSource, UUID.randomUUID());
+ public Set getTimeZone() {
+ return getTimeZones();
}
/**
@@ -1875,7 +2673,4 @@ public class Case implements SleuthkitCase.ErrorObserver {
deleteReports(reports);
}
- @Deprecated
- public static final String propStartup = "LBL_StartupDialog"; //NON-NLS
-
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseCloseAction.java
index 86cc38c251..19dc651f9a 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-2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,31 +19,33 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
+import java.awt.Cursor;
import java.awt.event.ActionEvent;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
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;
-import org.openide.util.actions.Presenter;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.ingest.IngestManager;
-import java.util.logging.Level;
-import org.openide.DialogDescriptor;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
-import org.openide.windows.WindowManager;
-import java.awt.Cursor;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+import org.openide.util.actions.CallableSystemAction;
+import org.openide.util.actions.Presenter;
+import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.IngestRunningCheck;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
/**
- * 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.
+ * The action associated with the Case/Close Case menu item and the Close Case
+ * toolbar button. It closes the current case and pops up the start up window
+ * that allows a user to open another case.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.casemodule.CaseCloseAction")
@ActionRegistration(displayName = "#CTL_CaseCloseAct", lazy = false)
@@ -51,87 +53,69 @@ import org.openide.awt.ActionRegistration;
@ActionReference(path = "Toolbars/Case", position = 104)})
public final class CaseCloseAction extends CallableSystemAction implements Presenter.Toolbar {
- JButton toolbarButton = new JButton();
+ private static final long serialVersionUID = 1L;
+ private static final Logger logger = Logger.getLogger(CaseCloseAction.class.getName());
+ private final JButton toolbarButton = new JButton();
/**
- * The constructor for this class
+ * Constructs the action associated with the Case/Close Case menu item and
+ * the Close Case toolbar button.
*/
public CaseCloseAction() {
- putValue("iconBase", "org/sleuthkit/autopsy/images/close-icon.png"); // put the icon NON-NLS
- putValue(Action.NAME, NbBundle.getMessage(CaseCloseAction.class, "CTL_CaseCloseAct")); // put the action Name
-
- // set action of the toolbar button
+ putValue("iconBase", "org/sleuthkit/autopsy/images/close-icon.png"); //NON-NLS
+ putValue(Action.NAME, NbBundle.getMessage(CaseCloseAction.class, "CTL_CaseCloseAct")); //NON-NLS
toolbarButton.addActionListener(CaseCloseAction.this::actionPerformed);
-
this.setEnabled(false);
}
/**
- * Closes the current opened case.
+ * Closes the current case.
*
- * @param e the action event for this method
+ * @param e The action event.
*/
@Override
public void actionPerformed(ActionEvent e) {
+ String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title");
+ String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning");
+ if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ new SwingWorker() {
- // if ingest is ongoing, warn and get confirmaion before opening a different case
- if (IngestManager.getInstance().isIngestRunning()) {
- // show the confirmation first to close the current case and open the "New Case" wizard panel
- String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning");
- NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(closeCurrentCase,
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"),
- NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
- descriptor.setValue(NotifyDescriptor.NO_OPTION);
-
- Object res = DialogDisplayer.getDefault().notify(descriptor);
- if (res != null && res == DialogDescriptor.YES_OPTION) {
- try {
- Case.getCurrentCase().closeCase(); // close the current case
- } catch (Exception ex) {
- Logger.getLogger(NewCaseWizardAction.class.getName()).log(Level.WARNING, "Error closing case.", ex); //NON-NLS
+ @Override
+ protected Void doInBackground() throws Exception {
+ Case.closeCurrentCase();
+ return null;
}
- } else {
- return;
- }
- }
- if (Case.isCaseOpen() == false) {
- return;
- }
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- new SwingWorker() {
-
- @Override
- protected Void doInBackground() throws Exception {
- try {
- Case result = Case.getCurrentCase();
- result.closeCase();
- } catch (CaseActionException | IllegalStateException unused) {
- // Already logged.
+ @Override
+ protected void done() {
+ try {
+ get();
+ } catch (InterruptedException ex) {
+ logger.log(Level.SEVERE, "Unexpected interrupt closing the current case", ex);
+ } catch (ExecutionException ex) {
+ logger.log(Level.SEVERE, "Error closing the current case", ex);
+ MessageNotifyUtil.Message.error(Bundle.Case_closeException_couldNotCloseCase(ex.getMessage()));
+ }
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ StartupWindowProvider.getInstance().open();
}
- return null;
- }
-
- @Override
- protected void done() {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- StartupWindowProvider.getInstance().open();
- }
- }.execute();
+ }.execute();
+ }
}
/**
- * This method does nothing. Use the "actionPerformed(ActionEvent e)"
- * instead of this method.
+ * Closes the current case.
*/
@Override
public void performAction() {
+ actionPerformed(null);
}
/**
- * Gets the name of this action. This may be presented as an item in a menu.
+ * Gets the action name.
*
- * @return actionName
+ * @return The action name.
*/
@Override
public String getName() {
@@ -139,9 +123,9 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese
}
/**
- * Gets the HelpCtx associated with implementing object
+ * Gets the help context.
*
- * @return HelpCtx or HelpCtx.DEFAULT_HELP
+ * @return The help context.
*/
@Override
public HelpCtx getHelpCtx() {
@@ -149,9 +133,9 @@ public final class CaseCloseAction extends CallableSystemAction implements Prese
}
/**
- * Returns the toolbar component of this action
+ * Returns the toolbar component of this action.
*
- * @return component the toolbar button
+ * @return The toolbar button
*/
@Override
public Component getToolbarPresenter() {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java
index 6ec7c79222..8a9f7bc17c 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,138 +19,106 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.event.ActionEvent;
-import java.io.File;
+import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
-import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.Action;
import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import org.openide.DialogDescriptor;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
-import org.openide.util.HelpCtx;
-import org.openide.util.NbBundle;
-import org.openide.util.actions.CallableSystemAction;
-;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import javax.swing.Action;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
+import javax.swing.SwingWorker;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.coreutils.Logger;
-import javax.swing.Action;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import org.openide.DialogDescriptor;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
-import org.openide.util.HelpCtx;
-import org.openide.util.NbBundle;
-import org.openide.util.actions.CallableSystemAction;
/**
- * The action to delete the current Case. This class should be disabled on
- * creation and it will be enabled on new case creation or case opened.
+ * The action associated with the Delete button of the Case Properties panel. It
+ * deletes the current case.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
-
-
final class CaseDeleteAction extends CallableSystemAction {
- private JPanel caller; // for error handling
-
+ private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(CaseDeleteAction.class.getName());
- /**
- * The constructor for this class
- */
- public CaseDeleteAction() {
- putValue(Action.NAME, NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction")); // put the action Name
+ CaseDeleteAction() {
+ putValue(Action.NAME, NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction"));
this.setEnabled(false);
}
- /**
- * Deletes the current opened case.
- *
- * @param e
- */
@Override
+ @Messages({
+ "Case.deleteCaseConfirmationDialog.title=Delete Current Case?",
+ "Case.deleteCaseConfirmationDialog.message=Are you sure you want to close and delete the current case?",
+ "Case.deleteCaseFailureMessageBox.title=Failed to Delete Case",
+ "# {0} - exception message", "Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}",})
public void actionPerformed(ActionEvent e) {
- Case currentCase = Case.getCurrentCase();
- File caseFolder = new File(currentCase.getCaseDirectory());
- String caseName = currentCase.getName();
- if (!caseFolder.exists()) {
- // throw an error
+ try {
+ Case currentCase = Case.getCurrentCase();
+ String caseName = currentCase.getName();
+ String caseDirectory = currentCase.getCaseDirectory();
- logger.log(Level.WARNING, "Couldn't delete case.", new Exception("The case directory doesn't exist.")); //NON-NLS
- } else {
- // show the confirmation first to close the current case and open the "New Case" wizard panel
- String closeCurrentCase = NbBundle.getMessage(this.getClass(), "CaseDeleteAction.closeConfMsg.text", caseName, caseFolder.getPath());
- NotifyDescriptor d = new NotifyDescriptor.Confirmation(closeCurrentCase,
- NbBundle.getMessage(this.getClass(),
- "CaseDeleteAction.closeConfMsg.title"),
- NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
- d.setValue(NotifyDescriptor.NO_OPTION);
+ /*
+ * Do a confirmation dialog and close the current case if the user
+ * confirms he/she wants to proceed.
+ */
+ Object response = DialogDisplayer.getDefault().notify(new NotifyDescriptor(
+ Bundle.Case_deleteCaseConfirmationDialog_message(),
+ Bundle.Case_deleteCaseConfirmationDialog_title(),
+ NotifyDescriptor.YES_NO_OPTION,
+ NotifyDescriptor.WARNING_MESSAGE,
+ null,
+ NotifyDescriptor.NO_OPTION));
+ if (null != response && DialogDescriptor.YES_OPTION == response) {
- Object res = DialogDisplayer.getDefault().notify(d);
- if (res != null && res == DialogDescriptor.YES_OPTION) {
- boolean success = false;
+ new SwingWorker() {
- try {
- Case.getCurrentCase().deleteCase(caseFolder); // delete the current case
- success = true;
- } catch (CaseActionException ex) {
- logger.log(Level.WARNING, "Could not delete the case folder: " + caseFolder); //NON-NLS
- }
+ @Override
+ protected Void doInBackground() throws Exception {
+ Case.deleteCurrentCase();
+ return null;
+ }
- // show notification whether the case has been deleted or it failed to delete...
- if (!success) {
- JOptionPane.showMessageDialog(caller,
- NbBundle.getMessage(this.getClass(),
- "CaseDeleteAction.msgDlg.fileInUse.msg"),
- NbBundle.getMessage(this.getClass(),
- "CaseDeleteAction.msgDlg.fileInUse.title"),
- JOptionPane.ERROR_MESSAGE); // throw an error
- } else {
- CasePropertiesAction.closeCasePropertiesWindow(); // because the "Delete Case" button is in the "CaseProperties" window, we have to close that window when we delete the case.
- JOptionPane.showMessageDialog(caller, NbBundle.getMessage(this.getClass(),
- "CaseDeleteAction.msgDlg.caseDelete.msg",
- caseName));
- }
+ @Override
+ protected void done() {
+ try {
+ get();
+ } catch (InterruptedException | ExecutionException ex) {
+ logger.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectory), ex);
+ JOptionPane.showMessageDialog(
+ null,
+ Bundle.Case_deleteCaseFailureMessageBox_message(ex.getMessage()),
+ Bundle.Case_deleteCaseFailureMessageBox_title(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ /*
+ * Close the Case Properties dialog that is the parent
+ * of the Delete button that invokes this action.
+ */
+ CasePropertiesAction.closeCasePropertiesWindow();
+ }
+ }.execute();
}
+ } catch (IllegalStateException ex) {
+ logger.log(Level.SEVERE, "Case delete action called with no current case", ex);
}
}
- /**
- * This method does nothing. Use the "actionPerformed(ActionEvent e)"
- * instead of this method.
- */
@Override
public void performAction() {
- // Note: I use the actionPerformed above instead of this method
}
- /**
- * Gets the name of this action. This may be presented as an item in a menu.
- *
- * @return actionName
- */
@Override
public String getName() {
return NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction");
}
- /**
- * Gets the HelpCtx associated with implementing object
- *
- * @return HelpCtx or HelpCtx.DEFAULT_HELP
- */
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java
index 15370e5d45..cc81d70a30 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java
@@ -54,7 +54,7 @@ class CaseInformationPanel extends javax.swing.JPanel {
// put the image paths information into hashmap
Map imgPaths = Case.getImagePaths(currentCase.getSleuthkitCase());
- CasePropertiesForm cpf = new CasePropertiesForm(currentCase, crDate, caseDir, imgPaths);
+ CasePropertiesPanel cpf = new CasePropertiesPanel(currentCase, crDate, caseDir, imgPaths);
cpf.setSize(cpf.getPreferredSize());
this.tabbedPane.addTab(Bundle.CaseInformationPanel_caseDetails_header(), cpf);
this.tabbedPane.addTab(Bundle.CaseInformationPanel_ingestJobInfo_header(), new IngestJobInfoPanel());
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
index bd07a3deaf..aaf65e2f7e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,10 +54,11 @@ public final class CaseMetadata {
private static final String FILE_EXTENSION = ".aut";
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)");
private static final String SCHEMA_VERSION_ONE = "1.0";
- private final static String AUTOPSY_CREATED_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS
+ private static final String SCHEMA_VERSION_TWO = "2.0";
+ private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS
private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS
private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS
- private static final String CURRENT_SCHEMA_VERSION = "2.0";
+ private static final String CURRENT_SCHEMA_VERSION = "3.0";
private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS
private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS
private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS
@@ -66,6 +67,7 @@ public final class CaseMetadata {
private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS
private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS
+ private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS
private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS
private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS
private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS
@@ -74,9 +76,10 @@ public final class CaseMetadata {
private final Path metadataFilePath;
private Case.CaseType caseType;
private String caseName;
+ private String caseDisplayName;
private String caseNumber;
private String examiner;
- private String caseDatabase;
+ private String caseDatabaseName;
private String textIndexName;
private String createdDate;
private String createdByVersion;
@@ -94,27 +97,28 @@ public final class CaseMetadata {
* Constructs an object that provides access to the case metadata stored in
* a new case metadata file that is created using the supplied metadata.
*
- * @param caseDirectory The case directory.
- * @param caseType The type of case.
- * @param caseName The name of the case.
- * @param caseNumber The case number.
- * @param examiner The name of the case examiner.
- * @param caseDatabase For a single-user case, the full path to the
- * case database file. For a multi-user case, the
- * case database name.
- * @param caseTextIndexName The text index name.
+ * @param caseDirectory The case directory.
+ * @param caseType The type of case.
+ * @param caseName The immutable name of the case.
+ * @param caseDisplayName The display name of the case, can be changed by a
+ * user.
+ * @param caseNumber The case number.
+ * @param examiner The name of the case examiner.
+ * @param caseDatabase For a single-user case, the full path to the case
+ * database file. For a multi-user case, the case
+ * database name.
*
* @throws CaseMetadataException If the new case metadata file cannot be
* created.
*/
- CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseNumber, String examiner, String caseDatabase, String caseTextIndexName) throws CaseMetadataException {
+ CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseDisplayName, String caseNumber, String examiner, String caseDatabase) throws CaseMetadataException {
metadataFilePath = Paths.get(caseDirectory, caseName + FILE_EXTENSION);
this.caseType = caseType;
this.caseName = caseName;
+ this.caseDisplayName = caseDisplayName;
this.caseNumber = caseNumber;
this.examiner = examiner;
- this.caseDatabase = caseDatabase;
- this.textIndexName = caseTextIndexName;
+ this.caseDatabaseName = caseDatabase;
createdByVersion = Version.getVersion();
createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
writeToFile();
@@ -162,7 +166,7 @@ public final class CaseMetadata {
}
/**
- * Gets the case display name.
+ * Gets the immutable case name, set at case creation.
*
* @return The case display name.
*/
@@ -170,19 +174,28 @@ public final class CaseMetadata {
return caseName;
}
+ /**
+ * Gets the case display name.
+ *
+ * @return The case display name.
+ */
+ public String getCaseDisplayName() {
+ return this.caseDisplayName;
+ }
+
/**
* Sets the case display name. This does not change the name of the case
* directory, the case database, or the text index name.
*
* @param caseName A case display name.
*/
- void setCaseName(String caseName) throws CaseMetadataException {
+ void setCaseDisplayName(String caseName) throws CaseMetadataException {
String oldCaseName = caseName;
- this.caseName = caseName;
+ this.caseDisplayName = caseName;
try {
writeToFile();
} catch (CaseMetadataException ex) {
- this.caseName = oldCaseName;
+ this.caseDisplayName = oldCaseName;
throw ex;
}
}
@@ -206,31 +219,27 @@ public final class CaseMetadata {
}
/**
- * Gets the name of the case case database.
+ * Gets the name of the case database.
*
* @return The case database name.
*/
public String getCaseDatabaseName() {
- if (caseType == Case.CaseType.MULTI_USER_CASE) {
- return caseDatabase;
- } else {
- return Paths.get(caseDatabase).getFileName().toString();
- }
+ return caseDatabaseName;
}
/**
- * Gets the full path to the case database file if the case is a single-user
- * case.
+ * Sets the text index name.
*
- * @return The full path to the case database file for a single-user case.
- *
- * @throws UnsupportedOperationException If called for a multi-user case.
+ * @param caseTextIndexName The text index name.
*/
- public String getCaseDatabasePath() throws UnsupportedOperationException {
- if (caseType == Case.CaseType.SINGLE_USER_CASE) {
- return caseDatabase;
- } else {
- throw new UnsupportedOperationException();
+ void setTextIndexName(String caseTextIndexName) throws CaseMetadataException {
+ String oldIndexName = caseTextIndexName;
+ this.textIndexName = caseTextIndexName;
+ try {
+ writeToFile();
+ } catch (CaseMetadataException ex) {
+ this.textIndexName = oldIndexName;
+ throw ex;
}
}
@@ -355,10 +364,11 @@ public final class CaseMetadata {
* Create the children of the case element.
*/
createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName);
+ createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDisplayName);
createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseNumber);
createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, examiner);
createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString());
- createChildElement(doc, caseElement, CASE_DATABASE_ELEMENT_NAME, caseDatabase);
+ createChildElement(doc, caseElement, CASE_DATABASE_ELEMENT_NAME, caseDatabaseName);
createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName);
}
@@ -402,7 +412,7 @@ public final class CaseMetadata {
String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true);
this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true);
if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
- this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_VERSION_ELEMENT_NAME, true);
+ this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true);
} else {
this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true);
}
@@ -416,6 +426,11 @@ public final class CaseMetadata {
}
Element caseElement = (Element) caseElements.item(0);
this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true);
+ if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) {
+ this.caseDisplayName = caseName;
+ } else {
+ this.caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true);
+ }
this.caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false);
this.examiner = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false);
this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
@@ -423,18 +438,21 @@ public final class CaseMetadata {
throw new CaseMetadataException("Case metadata file corrupted");
}
if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
- this.caseDatabase = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
+ this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true);
} else {
- this.caseDatabase = getElementTextContent(caseElement, CASE_DATABASE_ELEMENT_NAME, true);
- this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, true);
+ this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_ELEMENT_NAME, true);
+ this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
}
/*
- * Update the file to the current schema, if necessary.
+ * Fix up the case database name due to a bug that for a time caused
+ * the absolute paths of single-user case databases to be stored.
*/
- if (!schemaVersion.equals(CURRENT_SCHEMA_VERSION)) {
- writeToFile();
+ Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
+ if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
+ Path caseDirectoryPath = Paths.get(getCaseDirectory());
+ this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
}
} catch (ParserConfigurationException | SAXException | IOException ex) {
@@ -483,4 +501,22 @@ public final class CaseMetadata {
}
}
+ /**
+ * Gets the full path to the case database file if the case is a single-user
+ * case.
+ *
+ * @return The full path to the case database file for a single-user case.
+ *
+ * @throws UnsupportedOperationException If called for a multi-user case.
+ * @deprecated
+ */
+ @Deprecated
+ public String getCaseDatabasePath() throws UnsupportedOperationException {
+ if (Case.CaseType.SINGLE_USER_CASE == caseType) {
+ return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java
index 4e8243d3ba..6e3acaceec 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,27 +26,25 @@ import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.ServiceProvider;
/**
- * The action to create a new case. This action class is always enabled.
- *
- * @author jantonius
+ * The action associated with the Case/New Case menu item and the Create New
+ * Case button of the start up window that allows a user to open a case. It
+ * invokes the New Case wizard.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
@ServiceProvider(service = CaseNewActionInterface.class)
public final class CaseNewAction extends CallableSystemAction implements CaseNewActionInterface {
- private NewCaseWizardAction wizard = SystemAction.get(NewCaseWizardAction.class);
+ private static final long serialVersionUID = 1L;
- /**
- * Calls the "New Case" wizard panel action.
- *
- * @param e
- */
@Override
public void actionPerformed(ActionEvent e) {
- wizard.performAction();
+ SystemAction.get(NewCaseWizardAction.class).performAction();
}
@Override
public void performAction() {
+ actionPerformed(null);
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java
index 8a4f4b6c6f..26a7fb9c26 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-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,39 +22,45 @@ import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
+import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
+import org.openide.util.actions.CallableSystemAction;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.IngestRunningCheck;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.Version;
-import org.openide.DialogDescriptor;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import java.util.logging.Level;
-import org.openide.util.HelpCtx;
-import org.openide.util.actions.CallableSystemAction;
-import org.sleuthkit.autopsy.ingest.IngestManager;
/**
- * An action that opens an existing case.
+ * The action associated with the Case/Open Case menu item via the layer.xml
+ * file, a toolbar button, and the Create New Case button of the start up window
+ * that allows a user to open a case. It opens an existing case.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
@ServiceProvider(service = CaseOpenAction.class)
public final class CaseOpenAction extends CallableSystemAction implements ActionListener {
- private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName());
- private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS
private static final long serialVersionUID = 1L;
+ private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; //NON-NLS
+ private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName());
private final JFileChooser fileChooser = new JFileChooser();
private final FileFilter caseMetadataFileFilter;
/**
- * Constructs an action that opens an existing case.
+ * Constructs the action associated with the Case/Open Case menu item via
+ * the layer.xml file, a toolbar button, and the Create New Case button of
+ * the start up window that allows a user to open a case. It opens an
+ * existing case.
+ *
*/
public CaseOpenAction() {
caseMetadataFileFilter = new FileNameExtensionFilter(NbBundle.getMessage(CaseOpenAction.class, "CaseOpenAction.autFilter.title", Version.getName(), CaseMetadata.getFileExtension()), CaseMetadata.getFileExtension().substring(1));
@@ -75,77 +81,69 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
*/
@Override
public void actionPerformed(ActionEvent e) {
- /*
- * If ingest is running, do a dialog to warn the user and confirm the
- * intent to close the current case and leave the ingest process
- * incomplete.
- */
- if (IngestManager.getInstance().isIngestRunning()) {
- NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"),
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"),
- NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
- descriptor.setValue(NotifyDescriptor.NO_OPTION);
- Object res = DialogDisplayer.getDefault().notify(descriptor);
- if (res != null && res == DialogDescriptor.YES_OPTION) {
- Case currentCase = null;
- try {
- currentCase = Case.getCurrentCase();
- currentCase.closeCase();
- } catch (IllegalStateException ignored) {
- /*
- * No current case.
- */
- } catch (CaseActionException ex) {
- logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null != currentCase ? currentCase.getCaseDirectory() : "?")), ex); //NON-NLS
- }
- } else {
- return;
- }
- }
-
- /**
- * 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) {
- /*
- * Close the startup window, if it is open.
+ String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title");
+ String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning");
+ if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
+ /**
+ * Pop up a file chooser to allow the user to select a case metadata
+ * file (.aut file).
*/
- StartupWindowProvider.getInstance().close();
+ int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow());
+ if (retval == JFileChooser.APPROVE_OPTION) {
+ /*
+ * Close the startup window, if it is open.
+ */
+ StartupWindowProvider.getInstance().close();
- /*
- * Try to open the case associated with the case metadata 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)));
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- new Thread(() -> {
- try {
- Case.open(path);
- } catch (CaseActionException ex) {
- logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- JOptionPane.showMessageDialog(
- WindowManager.getDefault().getMainWindow(),
- ex.getMessage(), // Should be user-friendly
- NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
- JOptionPane.ERROR_MESSAGE);
- if (!Case.isCaseOpen()) {
+ /*
+ * Try to open the case associated with the case metadata 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)));
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ new SwingWorker() {
+
+ @Override
+ protected Void doInBackground() throws Exception {
+ Case.openAsCurrentCase(path);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ get();
+ } catch (InterruptedException ex) {
+ logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ JOptionPane.showMessageDialog(
+ WindowManager.getDefault().getMainWindow(),
+ ex.getMessage(),
+ NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
+ JOptionPane.ERROR_MESSAGE);
+ StartupWindowProvider.getInstance().open();
+ } catch (ExecutionException ex) {
+ logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", path), ex); //NON-NLS
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ JOptionPane.showMessageDialog(
+ WindowManager.getDefault().getMainWindow(),
+ ex.getCause().getMessage(), //get the message of the wrapped exception
+ NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
+ JOptionPane.ERROR_MESSAGE);
StartupWindowProvider.getInstance().open();
}
- });
- }
- }).start();
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ }.execute();
+ }
}
}
@Override
public void performAction() {
+ actionPerformed(null);
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java
index 462a60d96a..3f80544101 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,98 +23,70 @@ import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
-import java.util.logging.Level;
import javax.swing.Action;
import javax.swing.JDialog;
-import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
-import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * The action to pop up the Case Properties Form window. By using this form,
- * user can update the case properties (for example: updates the case name and
- * removes the image from the current case)
- *
- * @author jantonius
+ * The action associated with the Case/Case Properties menu item. It invokes the
+ * Case Properties dialog.
*/
final class CasePropertiesAction extends CallableSystemAction {
- private static JDialog popUpWindow;
+ private static final long serialVersionUID = 1L;
+ private static JDialog casePropertiesDialog;
- /**
- * The CasePropertiesAction constructor
- */
CasePropertiesAction() {
- putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction")); // put the action Name
+ putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction"));
this.setEnabled(false);
Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
- popUpWindow = null;
+ setEnabled(null != evt.getNewValue());
}
});
}
- /**
- * Pop-up the Case Properties Form window where user can change the case
- * properties (example: update case name and remove the image from the case)
- */
@Override
public void performAction() {
- if (popUpWindow == null) {
- // create the popUp window for it
- String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title");
- popUpWindow = new JDialog((JFrame) WindowManager.getDefault().getMainWindow(), title, false);
- try {
-
+ SwingUtilities.invokeLater(() -> {
+ if (null == casePropertiesDialog) {
+ String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title");
+ casePropertiesDialog = new JDialog(WindowManager.getDefault().getMainWindow(), title, false);
CaseInformationPanel caseInformationPanel = new CaseInformationPanel();
caseInformationPanel.addCloseButtonAction((ActionEvent e) -> {
- popUpWindow.dispose();
+ casePropertiesDialog.setVisible(false);
});
+ casePropertiesDialog.add(caseInformationPanel);
+ casePropertiesDialog.setResizable(true);
+ casePropertiesDialog.pack();
- popUpWindow.add(caseInformationPanel);
- popUpWindow.setResizable(true);
- popUpWindow.pack();
-
- // set the location of the popUp Window on the center of the screen
Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
- double w = popUpWindow.getSize().getWidth();
- double h = popUpWindow.getSize().getHeight();
- popUpWindow.setLocation((int) ((screenDimension.getWidth() - w) / 2), (int) ((screenDimension.getHeight() - h) / 2));
-
- popUpWindow.setVisible(true);
- } catch (Exception ex) {
- Logger.getLogger(CasePropertiesAction.class.getName()).log(Level.WARNING, "Error displaying Case Properties window.", ex); //NON-NLS
+ double w = casePropertiesDialog.getSize().getWidth();
+ double h = casePropertiesDialog.getSize().getHeight();
+ casePropertiesDialog.setLocation((int) ((screenDimension.getWidth() - w) / 2), (int) ((screenDimension.getHeight() - h) / 2));
+ casePropertiesDialog.setVisible(true);
}
- }
- popUpWindow.setVisible(true);
- popUpWindow.toFront();
+ casePropertiesDialog.setVisible(true);
+ casePropertiesDialog.toFront();
+ });
}
- /**
- * Gets the name of this action. This may be presented as an item in a menu.
- *
- * @return actionName
- */
@Override
public String getName() {
return NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction");
}
- /**
- * Gets the HelpCtx associated with implementing object
- *
- * @return HelpCtx or HelpCtx.DEFAULT_HELP
- */
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
static void closeCasePropertiesWindow() {
- popUpWindow.dispose();
+ casePropertiesDialog.setVisible(false);
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.form
similarity index 91%
rename from Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form
rename to Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.form
index e2b4940105..1d46bebfbb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.form
@@ -161,7 +161,7 @@
-
+
@@ -173,7 +173,7 @@
-
+
@@ -185,7 +185,7 @@
-
+
@@ -197,12 +197,9 @@
-
+
-
-
-
@@ -212,7 +209,7 @@
-
+
@@ -227,7 +224,7 @@
-
+
@@ -242,7 +239,7 @@
-
+
@@ -254,7 +251,7 @@
-
+
@@ -266,7 +263,7 @@
-
+
@@ -278,7 +275,7 @@
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java
similarity index 77%
rename from Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java
rename to Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java
index f4cc9cb24e..753033fe51 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,14 +17,9 @@
* limitations under the License.
*/
- /*
- * CasePropertiesForm.java
- *
- * Created on Mar 14, 2011, 1:48:20 PM
- */
package org.sleuthkit.autopsy.casemodule;
-import java.io.File;
+import java.nio.file.Paths;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JOptionPane;
@@ -37,55 +32,26 @@ import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * The form where user can change / update the properties of the current case
- * metadata.
+ * A panel that allows the user to view various properties of the current case
+ * and change the display name of the case.
*/
-class CasePropertiesForm extends javax.swing.JPanel {
-
+class CasePropertiesPanel extends javax.swing.JPanel {
+
private static final long serialVersionUID = 1L;
-
private Case current = null;
private static JPanel caller; // panel for error
- // Shrink a path to fit in targetLength (if necessary), by replaceing part
- // of the path with "...". Ex: "C:\Users\bob\...\folder\other\Image.img"
- private String shrinkPath(String path, int targetLength) {
- if (path.length() > targetLength) {
- String fill = "...";
-
- int partsLength = targetLength - fill.length();
-
- String front = path.substring(0, partsLength / 4);
- int frontSep = front.lastIndexOf(File.separatorChar);
- if (frontSep != -1) {
- front = front.substring(0, frontSep + 1);
- }
-
- String back = path.substring(partsLength * 3 / 4);
- int backSep = back.indexOf(File.separatorChar);
- if (backSep != -1) {
- back = back.substring(backSep);
- }
- return back + fill + front;
- } else {
- return path;
- }
- }
-
- /**
- * Creates new form CasePropertiesForm
- */
- CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException {
+ CasePropertiesPanel(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException {
initComponents();
- caseNameTextField.setText(currentCase.getName());
+ caseNameTextField.setText(currentCase.getDisplayName());
String caseNumber = currentCase.getNumber();
- if (!caseNumber.equals("")) {
+ if (!caseNumber.isEmpty()) {
caseNumberField.setText(caseNumber);
} else {
caseNumberField.setText("N/A");
}
String examiner = currentCase.getExaminer();
- if (!examiner.equals("")) {
+ if (!examiner.isEmpty()) {
examinerField.setText(examiner);
} else {
examinerField.setText("N/A");
@@ -93,10 +59,10 @@ class CasePropertiesForm extends javax.swing.JPanel {
crDateField.setText(crDate);
caseDirField.setText(caseDir);
current = currentCase;
-
+
CaseMetadata caseMetadata = currentCase.getCaseMetadata();
if (caseMetadata.getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
- dbNameField.setText(caseMetadata.getCaseDatabasePath());
+ dbNameField.setText(Paths.get(caseMetadata.getCaseDirectory(), caseMetadata.getCaseDatabaseName()).toString());
} else {
dbNameField.setText(caseMetadata.getCaseDatabaseName());
}
@@ -152,24 +118,19 @@ class CasePropertiesForm extends javax.swing.JPanel {
jScrollPane1.setViewportView(jTextArea1);
caseNameLabel.setFont(caseNameLabel.getFont().deriveFont(caseNameLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- caseNameLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameLabel.text")); // NOI18N
+ caseNameLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseNameLabel.text")); // NOI18N
crDateLabel.setFont(crDateLabel.getFont().deriveFont(crDateLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- crDateLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.crDateLabel.text")); // NOI18N
+ crDateLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.crDateLabel.text")); // NOI18N
caseDirLabel.setFont(caseDirLabel.getFont().deriveFont(caseDirLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- caseDirLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseDirLabel.text")); // NOI18N
+ caseDirLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseDirLabel.text")); // NOI18N
caseNameTextField.setFont(caseNameTextField.getFont().deriveFont(caseNameTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- caseNameTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameTextField.text")); // NOI18N
- caseNameTextField.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- caseNameTextFieldActionPerformed(evt);
- }
- });
+ caseNameTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseNameTextField.text")); // NOI18N
updateCaseNameButton.setFont(updateCaseNameButton.getFont().deriveFont(updateCaseNameButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- updateCaseNameButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.updateCaseNameButton.text")); // NOI18N
+ updateCaseNameButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.updateCaseNameButton.text")); // NOI18N
updateCaseNameButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
updateCaseNameButtonActionPerformed(evt);
@@ -177,7 +138,7 @@ class CasePropertiesForm extends javax.swing.JPanel {
});
deleteCaseButton.setFont(deleteCaseButton.getFont().deriveFont(deleteCaseButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- deleteCaseButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.deleteCaseButton.text")); // NOI18N
+ deleteCaseButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.deleteCaseButton.text")); // NOI18N
deleteCaseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deleteCaseButtonActionPerformed(evt);
@@ -185,16 +146,16 @@ class CasePropertiesForm extends javax.swing.JPanel {
});
caseNumberLabel.setFont(caseNumberLabel.getFont().deriveFont(caseNumberLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- caseNumberLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNumberLabel.text")); // NOI18N
+ caseNumberLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.caseNumberLabel.text")); // NOI18N
examinerLabel.setFont(examinerLabel.getFont().deriveFont(examinerLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- examinerLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.examinerLabel.text")); // NOI18N
+ examinerLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.examinerLabel.text")); // NOI18N
lbDbType.setFont(lbDbType.getFont().deriveFont(lbDbType.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- lbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbType.text")); // NOI18N
+ lbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.lbDbType.text")); // NOI18N
lbDbName.setFont(lbDbName.getFont().deriveFont(lbDbName.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
- lbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbName.text")); // NOI18N
+ lbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesPanel.class, "CasePropertiesPanel.lbDbName.text")); // NOI18N
caseDirField.setMinimumSize(new java.awt.Dimension(25, 14));
@@ -303,13 +264,13 @@ class CasePropertiesForm extends javax.swing.JPanel {
* @param evt The action event
*/
private void updateCaseNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateCaseNameButtonActionPerformed
- String oldCaseName = Case.getCurrentCase().getName();
+ String oldCaseName = Case.getCurrentCase().getDisplayName();
String newCaseName = caseNameTextField.getText();
// check if the old and new case name is not equal
if (!oldCaseName.equals(newCaseName)) {
// check if the case name is empty
- if (newCaseName.trim().equals("")) {
+ if (newCaseName.trim().isEmpty()) {
JOptionPane.showMessageDialog(caller,
NbBundle.getMessage(this.getClass(),
"CasePropertiesForm.updateCaseName.msgDlg.empty.msg"),
@@ -318,34 +279,36 @@ class CasePropertiesForm extends javax.swing.JPanel {
JOptionPane.ERROR_MESSAGE);
} else // check if case Name contain one of this following symbol:
// \ / : * ? " < > |
- if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":")
- || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"")
- || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) {
- String errorMsg = NbBundle
- .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg");
- JOptionPane.showMessageDialog(caller, errorMsg,
- NbBundle.getMessage(this.getClass(),
- "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"),
- JOptionPane.ERROR_MESSAGE);
- } else {
- // ask for the confirmation first
- String confMsg = NbBundle
- .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName,
- newCaseName);
- NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg,
- NbBundle.getMessage(this.getClass(),
- "CasePropertiesForm.updateCaseName.confMsg.title"),
- NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
- d.setValue(NotifyDescriptor.NO_OPTION);
-
- Object res = DialogDisplayer.getDefault().notify(d);
- if (res != null && res == DialogDescriptor.YES_OPTION) {
- // if user select "Yes"
- String oldPath = current.getCaseMetadata().getFilePath().toString();
- try {
- current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath);
- } catch (Exception ex) {
- Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS
+ {
+ if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":")
+ || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"")
+ || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) {
+ String errorMsg = NbBundle
+ .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg");
+ JOptionPane.showMessageDialog(caller, errorMsg,
+ NbBundle.getMessage(this.getClass(),
+ "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"),
+ JOptionPane.ERROR_MESSAGE);
+ } else {
+ // ask for the confirmation first
+ String confMsg = NbBundle
+ .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName,
+ newCaseName);
+ NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg,
+ NbBundle.getMessage(this.getClass(),
+ "CasePropertiesForm.updateCaseName.confMsg.title"),
+ NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
+ d.setValue(NotifyDescriptor.NO_OPTION);
+
+ Object res = DialogDisplayer.getDefault().notify(d);
+ if (res != null && res == DialogDescriptor.YES_OPTION) {
+ // if user select "Yes"
+ String oldPath = current.getCaseMetadata().getFilePath().toString();
+ try {
+ current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath);
+ } catch (CaseActionException ex) {
+ Logger.getLogger(CasePropertiesPanel.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS
+ }
}
}
}
@@ -356,10 +319,6 @@ class CasePropertiesForm extends javax.swing.JPanel {
CallableSystemAction.get(CaseDeleteAction.class).actionPerformed(evt);
}//GEN-LAST:event_deleteCaseButtonActionPerformed
- private void caseNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseNameTextFieldActionPerformed
- // TODO add your handling code here:
- }//GEN-LAST:event_caseNameTextFieldActionPerformed
-
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel caseDirField;
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java
index bdb3ae29de..6042c24d12 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -77,8 +77,13 @@ final class CollaborationMonitor {
* collaborating nodes, informs the user of collaboration tasks on other
* nodes using progress bars, and monitors the health of key collaboration
* services.
+ *
+ * @param eventChannelPrefix The prefix for the remote events channel.
+ *
+ * @throws
+ * org.sleuthkit.autopsy.casemodule.CollaborationMonitor.CollaborationMonitorException
*/
- CollaborationMonitor() throws CollaborationMonitorException {
+ CollaborationMonitor(String eventChannelPrefix) throws CollaborationMonitorException {
/**
* Get the local host name so it can be used to identify the source of
* collaboration tasks broadcast by this node.
@@ -91,9 +96,7 @@ final class CollaborationMonitor {
*/
eventPublisher = new AutopsyEventPublisher();
try {
- Case openedCase = Case.getCurrentCase();
- String channelPrefix = openedCase.getTextIndexName();
- eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, channelPrefix));
+ eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, eventChannelPrefix));
} catch (AutopsyEventException ex) {
throw new CollaborationMonitorException("Failed to initialize", ex);
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java
index 22c2485b16..956eb5fa54 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-2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,34 +18,41 @@
*/
package org.sleuthkit.autopsy.casemodule;
-import java.awt.*;
+import java.awt.Dialog;
+import java.awt.Dimension;
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;
+import org.openide.windows.WindowManager;
-/**
- *
+/*
+ * The panel in the default Autopsy startup window.
*/
public class CueBannerPanel extends javax.swing.JPanel {
- final private static String title = NbBundle.getMessage(CueBannerPanel.class, "CueBannerPanel.title.text");
- final private static JFrame frame = new JFrame(title);
- final static JDialog recentCasesWindow = new JDialog(frame, title, true); // to make the popUp Window to be modal
+ private static final long serialVersionUID = 1L;
+ /*
+ * This is field is static for the sake of the closeOpenRecentCasesWindow
+ * method.
+ */
+ private static JDialog recentCasesWindow;
- // for error handling
- private static JPanel caller = new JPanel();
+ public static void closeOpenRecentCasesWindow() {
+ if (null != recentCasesWindow) {
+ recentCasesWindow.setVisible(false);
+ }
+ }
public CueBannerPanel() {
initComponents();
- refresh();
+ customizeComponents();
+ enableComponents();
}
public CueBannerPanel(String welcomeLogo) {
@@ -55,7 +62,53 @@ public class CueBannerPanel extends javax.swing.JPanel {
ImageIcon icon = new ImageIcon(cl.getResource(welcomeLogo));
autopsyLogo.setIcon(icon);
}
- refresh();
+ customizeComponents();
+ enableComponents();
+ }
+
+ public void setCloseButtonActionListener(ActionListener e) {
+ closeButton.addActionListener(e);
+ }
+
+ public void setCloseButtonText(String text) {
+ closeButton.setText(text);
+ }
+
+ public void refresh() {
+ enableComponents();
+ }
+
+ private void customizeComponents() {
+ recentCasesWindow = new JDialog(
+ WindowManager.getDefault().getMainWindow(),
+ NbBundle.getMessage(CueBannerPanel.class, "CueBannerPanel.title.text"),
+ Dialog.ModalityType.APPLICATION_MODAL);
+ recentCasesWindow.setSize(new Dimension(750, 400));
+ recentCasesWindow.getRootPane().registerKeyboardAction(
+ e -> {
+ recentCasesWindow.setVisible(false);
+ },
+ KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+ OpenRecentCasePanel recentCasesPanel = OpenRecentCasePanel.getInstance();
+ recentCasesPanel.setCloseButtonActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ recentCasesWindow.setVisible(false);
+ }
+ });
+ recentCasesWindow.add(recentCasesPanel);
+ recentCasesWindow.pack();
+ recentCasesWindow.setResizable(false);
+ }
+
+ private void enableComponents() {
+ if (RecentCases.getInstance().getTotalRecentCases() == 0) {
+ openRecentButton.setEnabled(false);
+ openRecentLabel.setEnabled(false);
+ } else {
+ openRecentButton.setEnabled(true);
+ openRecentLabel.setEnabled(true);
+ }
}
/**
@@ -180,15 +233,6 @@ public class CueBannerPanel extends javax.swing.JPanel {
);
}// //GEN-END:initComponents
- public void refresh() {
- if (RecentCases.getInstance().getTotalRecentCases() == 0) {
- openRecentButton.setEnabled(false);
- openRecentLabel.setEnabled(false);
- } else {
- openRecentButton.setEnabled(true);
- openRecentLabel.setEnabled(true);
- }
- }
private void newCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newCaseButtonActionPerformed
Lookup.getDefault().lookup(CaseNewActionInterface.class).actionPerformed(evt);
}//GEN-LAST:event_newCaseButtonActionPerformed
@@ -198,37 +242,8 @@ public class CueBannerPanel extends javax.swing.JPanel {
}//GEN-LAST:event_openCaseButtonActionPerformed
private void openRecentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openRecentButtonActionPerformed
-
- // open the recent cases dialog
- Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
-
- // set the popUp window / JFrame
- recentCasesWindow.setSize(750, 400);
-
- int w = recentCasesWindow.getSize().width;
- int h = recentCasesWindow.getSize().height;
-
- // 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();
-
- // add the command to close the window to the button on the Volume Detail Panel
- welcomeWindow.setCloseButtonActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- recentCasesWindow.dispose();
- }
- });
-
- recentCasesWindow.add(welcomeWindow);
- recentCasesWindow.pack();
- recentCasesWindow.setResizable(false);
- recentCasesWindow.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
+ recentCasesWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
+ OpenRecentCasePanel.getInstance(); //refreshes the recent cases table
recentCasesWindow.setVisible(true);
}//GEN-LAST:event_openRecentButtonActionPerformed
@@ -244,29 +259,4 @@ public class CueBannerPanel extends javax.swing.JPanel {
private javax.swing.JLabel openRecentLabel;
// End of variables declaration//GEN-END:variables
- /**
- * Sets the Close button action listener.
- *
- * @param e the action listener
- */
- public void setCloseButtonActionListener(ActionListener e) {
- closeButton.addActionListener(e);
- }
-
- /**
- * Sets the Close button label (default is "Close").
- *
- * @param text The new label for the button.
- */
- public void setCloseButtonText(String text) {
- closeButton.setText(text);
- }
-
- /**
- * Close the open recent cases window.
- */
- public static void closeOpenRecentCasesWindow() {
- //startupWindow.setVisible(false);
- recentCasesWindow.dispose();
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
index 8837c496d0..5d976c0baa 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
@@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.DataSourceUtils;
-import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
/**
* A image file data source processor that implements the DataSourceProcessor
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
index 80d1306598..d5b926d197 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
@@ -30,7 +30,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.DriveUtils;
-import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
/**
* A local drive data source processor that implements the DataSourceProcessor
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java
index 9c19cb2cfc..e207734714 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java
@@ -29,7 +29,7 @@ import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
-import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor;
/**
* A local/logical files and/or directories data source processor that
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java
index 39f831c5a1..128ea4a8c3 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-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,31 +19,33 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
+import java.awt.Cursor;
import java.awt.Dialog;
import java.io.File;
import java.text.MessageFormat;
+import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.JComponent;
+import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
-import javax.swing.SwingUtilities;
-import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
import org.openide.WizardDescriptor;
import org.openide.util.HelpCtx;
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.openide.windows.WindowManager;
-import java.awt.Cursor;
-import java.util.concurrent.ExecutionException;
-import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.actions.IngestRunningCheck;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
+import org.sleuthkit.autopsy.coreutils.FileUtil;
+import org.sleuthkit.autopsy.coreutils.Logger;
/**
- * An action that creates and runs the new case wizard.
+ * The action associated with the Case/New Case menu item, t toolbar button, and
+ * the button in the start up window that allows users to open cases action. It
+ * runs first the New Case wizard, then the Add Data Source wizard.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
final class NewCaseWizardAction extends CallableSystemAction {
@@ -53,39 +55,15 @@ final class NewCaseWizardAction extends CallableSystemAction {
@Override
public void performAction() {
- /*
- * If ingest is running, do a dialog to warn the user and confirm the
- * intent to close the current case and leave the ingest process
- * incomplete.
- */
- if (IngestManager.getInstance().isIngestRunning()) {
- NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"),
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"),
- NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
- descriptor.setValue(NotifyDescriptor.NO_OPTION);
- Object res = DialogDisplayer.getDefault().notify(descriptor);
- if (res != null && res == DialogDescriptor.YES_OPTION) {
- Case currentCase = null;
- try {
- currentCase = Case.getCurrentCase();
- currentCase.closeCase();
- } catch (IllegalStateException ignored) {
- /*
- * No current case.
- */
- } catch (CaseActionException ex) {
- logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null != currentCase ? currentCase.getCaseDirectory() : "?")), ex); //NON-NLS
- }
- } else {
- return;
- }
+ String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title");
+ String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning");
+ if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
+ runNewCaseWizard();
}
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- runNewCaseWizard();
}
private void runNewCaseWizard() {
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
final WizardDescriptor wizardDescriptor = new WizardDescriptor(getNewCaseWizardPanels());
wizardDescriptor.setTitleFormat(new MessageFormat("{0}"));
wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text"));
@@ -101,7 +79,7 @@ final class NewCaseWizardAction extends CallableSystemAction {
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);
+ Case.createAsCurrentCase(createdDirectory, caseName, caseNumber, examiner, caseType);
return null;
}
@@ -109,26 +87,29 @@ final class NewCaseWizardAction extends CallableSystemAction {
protected void done() {
try {
get();
+ /*
+ * Run the Add Data Source wizard by invoking the Add
+ * Data Source wizard.
+ */
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
addImageAction.actionPerformed(null);
- } catch (Exception ex) {
+ } catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, String.format("Error creating case %s", wizardDescriptor.getProperty("caseName")), ex); //NON-NLS
- SwingUtilities.invokeLater(() -> {
- JOptionPane.showMessageDialog(
- WindowManager.getDefault().getMainWindow(),
- (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()),
- NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS
- JOptionPane.ERROR_MESSAGE);
- StartupWindowProvider.getInstance().close(); // RC: Why close and open?
- if (!Case.isCaseOpen()) {
- StartupWindowProvider.getInstance().open();
- }
- });
+ JOptionPane.showMessageDialog(
+ WindowManager.getDefault().getMainWindow(),
+ (ex instanceof ExecutionException ? ex.getCause().getMessage() : ex.getMessage()),
+ NbBundle.getMessage(this.getClass(), "CaseCreateAction.msgDlg.cantCreateCase.msg"), //NON-NLS
+ JOptionPane.ERROR_MESSAGE);
+ StartupWindowProvider.getInstance().close();
+ StartupWindowProvider.getInstance().open();
doFailedCaseCleanup(wizardDescriptor);
+ } finally {
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}.execute();
} else {
+ WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
new Thread(() -> {
doFailedCaseCleanup(wizardDescriptor);
}).start();
@@ -138,11 +119,8 @@ final class NewCaseWizardAction extends CallableSystemAction {
private void doFailedCaseCleanup(WizardDescriptor wizardDescriptor) {
String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
if (createdDirectory != null) {
- Case.deleteCaseDirectory(new File(createdDirectory));
+ FileUtil.deleteDir(new File(createdDirectory));
}
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- });
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java
index 24c1e2a17e..737cbe8839 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,9 +23,6 @@ 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;
import javax.swing.event.ChangeListener;
import org.openide.DialogDescriptor;
@@ -34,7 +31,10 @@ import org.openide.NotifyDescriptor;
import org.openide.WizardDescriptor;
import org.openide.WizardValidationException;
import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
+import org.sleuthkit.autopsy.coreutils.FileUtil;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/**
@@ -101,7 +101,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel listeners = new HashSet(1); // or can use ChangeSupport in NB 6.0
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
/**
* Adds a listener to changes of the panel's validity.
@@ -134,7 +134,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel it;
synchronized (listeners) {
- it = new HashSet(listeners).iterator();
+ it = new HashSet<>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
@@ -167,15 +167,15 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,9 +27,8 @@ import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.Logger;
import org.openide.windows.WindowManager;
-import java.awt.Cursor;
+import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Panel used by the the open recent case option of the start window.
@@ -103,7 +102,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
}
final String casePath = casePaths[imagesTable.getSelectedRow()];
final String caseName = caseNames[imagesTable.getSelectedRow()];
- if (!casePath.equals("")) {
+ if (!casePath.isEmpty()) {
try {
StartupWindowProvider.getInstance().close();
CueBannerPanel.closeOpenRecentCasesWindow();
@@ -114,34 +113,26 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
/*
* Open the case.
*/
- if (caseName.equals("") || casePath.equals("") || (!new File(casePath).exists())) {
+ if (caseName.isEmpty() || casePath.isEmpty() || (!new File(casePath).exists())) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName),
NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"),
JOptionPane.ERROR_MESSAGE);
RecentCases.getInstance().removeRecentCase(caseName, casePath); // remove the recent case if it doesn't exist anymore
- if (Case.isCaseOpen() == false) {
- StartupWindowProvider.getInstance().open();
- }
+ StartupWindowProvider.getInstance().open();
} else {
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- });
new Thread(() -> {
try {
- Case.open(casePath);
+ Case.openAsCurrentCase(casePath);
} catch (CaseActionException ex) {
+ logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS
SwingUtilities.invokeLater(() -> {
- logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", casePath), ex); //NON-NLS
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
JOptionPane.showMessageDialog(
WindowManager.getDefault().getMainWindow(),
ex.getMessage(), // Should be user-friendly
NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
- JOptionPane.ERROR_MESSAGE);
- if (!Case.isCaseOpen()) {
- StartupWindowProvider.getInstance().open();
- }
+ JOptionPane.ERROR_MESSAGE);
+ StartupWindowProvider.getInstance().open();
});
}
}).start();
@@ -160,7 +151,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel {
public int getRowCount() {
int count = 0;
for (String s : caseNames) {
- if (!s.equals("")) {
+ if (!s.isEmpty()) {
count++;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java
index 2204da444a..9ea9214c36 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,13 +30,13 @@ import java.util.List;
import java.util.logging.Level;
import javax.swing.JMenuItem;
import org.apache.commons.lang.ArrayUtils;
+import org.openide.filesystems.FileUtil;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.Presenter;
-import org.openide.filesystems.FileUtil;
-import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/**
* The action in this class is to clear the list of "Recent Cases". The
@@ -45,14 +45,13 @@ import org.sleuthkit.autopsy.coreutils.Logger;
*/
final class RecentCases extends CallableSystemAction implements Presenter.Menu {
- static final int LENGTH = 6;
- static final String NAME_PROP_KEY = "LBL_RecentCase_Name"; //NON-NLS
- static final String PATH_PROP_KEY = "LBL_RecentCase_Path"; //NON-NLS
- static final RecentCase BLANK_RECENTCASE = new RecentCase("", "");
-
- private final static RecentCases INSTANCE = new RecentCases();
-
- private Deque recentCases; // newest case is last case
+ private static final long serialVersionUID = 1L;
+ private static final int LENGTH = 6;
+ private static final String NAME_PROP_KEY = "LBL_RecentCase_Name"; //NON-NLS
+ private static final String PATH_PROP_KEY = "LBL_RecentCase_Path"; //NON-NLS
+ private static final RecentCase BLANK_RECENTCASE = new RecentCase("", "");
+ private final static RecentCases instance = new RecentCases();
+ private final Deque recentCases; // newest case is last case
/**
* Gets the instance of the RecentCases singleton.
@@ -61,8 +60,8 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
* @return INSTANCE the RecentCases singleton
*/
static public RecentCases getInstance() {
- INSTANCE.refreshRecentCases();
- return INSTANCE;
+ instance.refreshRecentCases();
+ return instance;
}
/**
@@ -84,7 +83,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
}
// Load recentCases from properties
- recentCases = new LinkedList();
+ recentCases = new LinkedList<>();
for (int i = 0; i < LENGTH; i++) {
final RecentCase rc = new RecentCase(getName(i), getPath(i));
@@ -175,7 +174,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
* @return true if the case exists, false otherwise
*/
boolean exists() {
- return !(name.equals("") || path.equals("") || !new File(path).exists());
+ return !(name.isEmpty() || path.isEmpty() || !new File(path).exists());
}
// netbeans autogenerated hashCode
@@ -211,7 +210,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
* exist.
*/
private void refreshRecentCases() {
- List toDelete = new ArrayList();
+ List toDelete = new ArrayList<>();
for (RecentCase rc : recentCases) {
if (!rc.exists()) {
toDelete.add(rc);
@@ -256,14 +255,14 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
*/
@Override
public void actionPerformed(ActionEvent e) {
- UpdateRecentCases.hasRecentCase = false;
+ UpdateRecentCases.setHasRecentCase(false);
recentCases.clear();
try {
// clear the properties file
storeRecentCases();
- } catch (Exception ex) {
+ } catch (IOException ex) {
Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not clear the properties file.", ex); //NON-NLS
}
}
@@ -297,7 +296,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
try {
storeRecentCases();
- } catch (Exception ex) {
+ } catch (IOException ex) {
Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not update the properties file.", ex); //NON-NLS
}
}
@@ -325,7 +324,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
try {
storeRecentCases();
- } catch (Exception ex) {
+ } catch (IOException ex) {
Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not update the properties file.", ex); //NON-NLS
}
}
@@ -357,7 +356,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
// write the properties file
try {
storeRecentCases();
- } catch (Exception ex) {
+ } catch (IOException ex) {
Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not update the properties file.", ex); //NON-NLS
}
}
@@ -375,7 +374,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu {
int i = 0;
String currentCaseName = null;
try {
- currentCaseName = Case.getCurrentCase().getName();
+ currentCaseName = Case.getCurrentCase().getDisplayName();
} catch (IllegalStateException ex) {
// in case there is no current case.
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentItems.java
index 2df4401ad9..d46fce31cb 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-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,111 +18,61 @@
*/
package org.sleuthkit.autopsy.casemodule;
-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.SwingUtilities;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
-import java.awt.Cursor;
-import java.util.logging.Level;
-import org.openide.DialogDescriptor;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
+import org.sleuthkit.autopsy.actions.IngestRunningCheck;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.ingest.IngestManager;
/**
- * An action listener that opens a recent case.
+ * An action listener for a specific case, associated with a Recent Cases menu
+ * item for the case by a DynamicMenuContent content JMenuItem.
+ *
+ * This action should only be invoked in the event dispatch thread (EDT).
*/
class RecentItems implements ActionListener {
private static final Logger logger = Logger.getLogger(RecentItems.class.getName());
- private final String caseName;
private final String caseMetaDataFilePath;
/**
- * Constructs an action listener that opens a recent case.
+ * Constructs an action listener for a specific case, associated with a
+ * Recent Cases menu item for the case by a DynamicMenuContent content
+ * JMenuItem.
*
* @param caseName The name of the case.
* @param caseMetaDataFilePath The path to the case metadata file.
*/
- public RecentItems(String caseName, String caseMetaDataFilePath) {
- this.caseName = caseName;
+ RecentItems(String caseName, String caseMetaDataFilePath) {
this.caseMetaDataFilePath = caseMetaDataFilePath;
}
/**
- * Opens the recent case.
+ * Opens the case associated with the action.
*
* @param e the action event
*/
@Override
public void actionPerformed(ActionEvent e) {
- /*
- * If ingest is running, do a dialog to warn the user and confirm the
- * intent to close the current case and leave the ingest process
- * incomplete.
- */
- if (IngestManager.getInstance().isIngestRunning()) {
- NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning"),
- NbBundle.getMessage(this.getClass(), "CloseCaseWhileIngesting.Warning.title"),
- NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
- descriptor.setValue(NotifyDescriptor.NO_OPTION);
- Object res = DialogDisplayer.getDefault().notify(descriptor);
- if (res != null && res == DialogDescriptor.YES_OPTION) {
- Case currentCase = null;
- try {
- currentCase = Case.getCurrentCase();
- currentCase.closeCase();
- } catch (IllegalStateException ignored) {
- /*
- * No current case.
- */
- } catch (CaseActionException ex) {
- logger.log(Level.SEVERE, String.format("Error closing case at %s while ingest was running", (null!= currentCase ? currentCase.getCaseDirectory() : "?")),ex); //NON-NLS
- }
- } else {
- return;
- }
- }
-
- /*
- * Open the case.
- */
- if (caseName.equals("") || caseMetaDataFilePath.equals("") || (!new File(caseMetaDataFilePath).exists())) {
- JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
- NbBundle.getMessage(this.getClass(), "RecentItems.openRecentCase.msgDlg.text", caseName),
- NbBundle.getMessage(this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"),
- JOptionPane.ERROR_MESSAGE);
- RecentCases.getInstance().removeRecentCase(caseName, caseMetaDataFilePath);
- if (Case.isCaseOpen() == false) {
- EventQueue.invokeLater(() -> {
- StartupWindowProvider.getInstance().open();
- });
- }
- } else {
- SwingUtilities.invokeLater(() -> {
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- });
+ String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title");
+ String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning");
+ if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
new Thread(() -> {
try {
- Case.open(caseMetaDataFilePath);
+ Case.openAsCurrentCase(caseMetaDataFilePath);
} catch (CaseActionException ex) {
+ logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS
SwingUtilities.invokeLater(() -> {
- logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetaDataFilePath), ex); //NON-NLS
- WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
JOptionPane.showMessageDialog(
WindowManager.getDefault().getMainWindow(),
- ex.getMessage(), // Should be user-friendly
+ ex.getMessage(),
NbBundle.getMessage(RecentItems.this.getClass(), "CaseOpenAction.msgDlg.cantOpenCase.title"), //NON-NLS
JOptionPane.ERROR_MESSAGE);
- if (!Case.isCaseOpen()) {
- StartupWindowProvider.getInstance().open();
- }
+ StartupWindowProvider.getInstance().open();
});
}
}).start();
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java
index e39f0d5ebb..6d57f3daac 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,19 +34,21 @@ import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
-import static org.sleuthkit.autopsy.casemodule.Case.MODULE_FOLDER;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
+import org.sleuthkit.autopsy.coreutils.NetworkUtils;
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
-import org.sleuthkit.autopsy.coreutils.NetworkUtils;
import org.sleuthkit.datamodel.TskData;
/**
* Import a case from single-user to multi-user.
+ *
+ * DO NOT USE, NEEDS TO BE UPDATED
*/
public class SingleUserCaseConverter {
+ private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
private static final String AUTOPSY_DB_FILE = "autopsy.db"; //NON-NLS
private static final String DOTAUT = CaseMetadata.getFileExtension(); //NON-NLS
private static final String TIMELINE_FOLDER = "Timeline"; //NON-NLS
@@ -176,7 +178,6 @@ public class SingleUserCaseConverter {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS
Date date = new Date();
String dbName = Case.sanitizeCaseName(icd.getNewCaseName()) + "_" + dateFormat.format(date); //NON-NLS
- String solrName = dbName;
icd.setPostgreSQLDbName(dbName);
// Copy items to new hostname folder structure
@@ -195,9 +196,10 @@ public class SingleUserCaseConverter {
CaseMetadata newCaseMetadata = new CaseMetadata(icd.getCaseOutputFolder().toString(),
CaseType.MULTI_USER_CASE,
icd.getNewCaseName(),
+ icd.getNewCaseName(),
oldCaseMetadata.getCaseNumber(),
oldCaseMetadata.getExaminer(),
- dbName, solrName);
+ dbName);
// Set created date. This calls writefile, no need to call it again
newCaseMetadata.setCreatedDate(oldCaseMetadata.getCreatedDate());
newCaseMetadata.setCreatedByVersion(oldCaseMetadata.getCreatedByVersion());
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java b/Core/src/org/sleuthkit/autopsy/casemodule/SleuthkitErrorReporter.java
similarity index 65%
rename from Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java
rename to Core/src/org/sleuthkit/autopsy/casemodule/SleuthkitErrorReporter.java
index 52ea056333..15deff10d6 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/IntervalErrorReportData.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/SleuthkitErrorReporter.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,51 +18,43 @@
*/
package org.sleuthkit.autopsy.casemodule;
+import java.util.logging.Level;
import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+import org.sleuthkit.datamodel.SleuthkitCase;
/**
- * 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.
+ * Acts as a bridge between the Sleuthkit Java bindings classes and Autopsy by
+ * implementing the SleuthkitCase$ErrorObserver interface. All errors are
+ * written to the Autopsy logs. If a GUI is running, errors are also batched up
+ * and reported periodically to the user via the notification area in the lower
+ * right hand corner of the main application window.
*/
-class IntervalErrorReportData {
+class SleuthkitErrorReporter implements SleuthkitCase.ErrorObserver {
- private final Case currentCase;
+ private static final Logger LOGGER = Logger.getLogger(SleuthkitErrorReporter.class.getName());
+ private final int milliSecondsBetweenReports;
+ private final String message;
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
+ * the user.
*/
- IntervalErrorReportData(Case currentCase, int secondsBetweenReports, String message) {
+ SleuthkitErrorReporter(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);
}
/**
@@ -73,18 +65,19 @@ class IntervalErrorReportData {
* @param context The context in which the error occurred.
* @param errorMessage A description of the error that occurred.
*/
- void addProblems(String context, String errorMessage) {
+ @Override
+ public void receiveError(String context, String errorMessage) {
+ LOGGER.log(Level.SEVERE, String.format("%s error in the SleuthKit layer: %s", context, 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")
+ + NbBundle.getMessage(SleuthkitErrorReporter.class, "IntervalErrorReport.NewIssues")
+ " " + this.totalProblems + " "
- + NbBundle.getMessage(IntervalErrorReportData.class, "IntervalErrorReport.TotalIssues")
+ + NbBundle.getMessage(SleuthkitErrorReporter.class, "IntervalErrorReport.TotalIssues")
+ ".");
this.newProblems = 0;
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java
index d609c9359f..8b94b0d441 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,25 +19,22 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Dimension;
-import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JDialog;
-import javax.swing.JFrame;
-
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.WindowManager;
/**
- * The default implementation of the Autopsy startup window
+ * The default implementation of the Autopsy startup window.
*/
@ServiceProvider(service = StartupWindowInterface.class)
public final class StartupWindow extends JDialog implements StartupWindowInterface {
- private static StartupWindow instance;
+ private static final long serialVersionUID = 1L;
private static final String TITLE = NbBundle.getMessage(StartupWindow.class, "StartupWindow.title.text");
- private static Dimension DIMENSIONS = new Dimension(750, 400);
+ private static final Dimension DIMENSIONS = new Dimension(750, 400);
private static CueBannerPanel welcomeWindow;
public StartupWindow() {
@@ -45,37 +42,18 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa
init();
}
- /**
- * Shows the startup window.
- */
private void init() {
-
- Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
-
- // set the popUp window / JFrame
setSize(DIMENSIONS);
- int w = this.getSize().width;
- int h = this.getSize().height;
-
- // 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();
-
- // add the command to close the window to the button on the Volume Detail Panel
welcomeWindow.setCloseButtonActionListener(new ActionListener() {
-
@Override
public void actionPerformed(ActionEvent e) {
close();
}
});
-
add(welcomeWindow);
pack();
setResizable(false);
-
}
@Override
@@ -85,9 +63,6 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa
setVisible(true);
}
- /**
- * Closes the startup window.
- */
@Override
public void close() {
this.setVisible(false);
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java
index 4d00a60606..29be82fe48 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-2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,17 +30,22 @@ import org.openide.util.actions.SystemAction;
*/
class UpdateRecentCases extends JMenuItem implements DynamicMenuContent {
- int length;
- static boolean hasRecentCase = false;
+ private static final long serialVersionUID = 1L;
+ private static int NUM_CASES_TO_DISPLAY;
+ private static boolean hasRecentCase = false;
/**
* the constructor
*/
UpdateRecentCases() {
// display last 5 cases.
- length = RecentCases.LENGTH - 1;
+ NUM_CASES_TO_DISPLAY = 5;
}
+ static void setHasRecentCase(boolean value) {
+ hasRecentCase = value;
+ }
+
/**
* Creates main menu/popup menu items. Null values will be later replaced by
* JSeparators. This method is called for popups and for menus. It's called
@@ -53,10 +58,10 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent {
public JComponent[] getMenuPresenters() {
String[] caseName = RecentCases.getInstance().getRecentCaseNames();
String[] casePath = RecentCases.getInstance().getRecentCasePaths();
- JComponent[] comps = new JComponent[length + 2]; // + 2 for separator and clear menu
+ JComponent[] comps = new JComponent[NUM_CASES_TO_DISPLAY + 2]; // + 2 for separator and clear menu
// if it has the recent menus, add them to the component list
- for (int i = 0; i < length; i++) {
+ for (int i = 0; i < NUM_CASES_TO_DISPLAY; i++) {
if ((!caseName[i].equals(""))) {
JMenuItem menuItem = new JMenuItem(caseName[i]);
menuItem.setActionCommand(caseName[i].toUpperCase());
@@ -68,11 +73,11 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent {
// if it has recent case, create clear menu
if (hasRecentCase) {
- comps[length] = new JSeparator();
+ comps[NUM_CASES_TO_DISPLAY] = new JSeparator();
JMenuItem clearMenu = new JMenuItem(
NbBundle.getMessage(UpdateRecentCases.class, "UpdateRecentCases.menuItem.clearRecentCases.text"));
clearMenu.addActionListener(SystemAction.get(RecentCases.class));
- comps[length + 1] = clearMenu;
+ comps[NUM_CASES_TO_DISPLAY + 1] = clearMenu;
} // otherwise, just create a disabled empty menu
else {
comps = new JComponent[1];
@@ -85,17 +90,15 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent {
}
/**
- * Updates main menu presenters. This method is called only by the main menu
- * processing.
+ * Updates the Recent Cases menu items.
*
- * @param jcs the previously used menu items returned by previous call to
- * getMenuPresenters() or synchMenuPresenters()
+ * @param menuItems A set of Recent Case menu items to be updated.
*
- * @return menu a new set of items to show in menu. Can be either an updated
- * old set of instances or a completely new one.
+ * @return A updated set of recent case menu items to show in the Recent
+ * Cases menu.
*/
@Override
- public JComponent[] synchMenuPresenters(JComponent[] jcs) {
+ public JComponent[] synchMenuPresenters(JComponent[] menuItems) {
return getMenuPresenters();
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java
index a8d11d184c..c38bc12144 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
* Copyright 2012 42six Solutions.
* Contact: aebadirad 42six com
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java
index 3d36e8ae00..39daa7419f 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java
@@ -1,21 +1,21 @@
/*
-* Autopsy Forensic Browser
-*
-* Copyright 2011-2016 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.
-*/
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2017 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package org.sleuthkit.autopsy.casemodule.services;
import java.beans.PropertyChangeEvent;
@@ -31,14 +31,14 @@ import org.openide.util.Lookup;
iconBase = "org/sleuthkit/autopsy/casemodule/services/tag-options-panel-icon.png",
keywords = "#OptionsCategory_TagNames",
keywordsCategory = "CustomTagNames",
- position = 8
+ position = 10
)
public final class TagsOptionsPanelController extends OptionsPanelController {
private TagOptionsPanel panel;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private boolean changed;
-
+
/**
* Component should load its data here.
*/
@@ -105,7 +105,7 @@ public final class TagsOptionsPanelController extends OptionsPanelController {
public void removePropertyChangeListener(PropertyChangeListener l) {
pcs.removePropertyChangeListener(l);
}
-
+
private TagOptionsPanel getPanel() {
if (panel == null) {
panel = new TagOptionsPanel();
@@ -117,7 +117,7 @@ public final class TagsOptionsPanelController extends OptionsPanelController {
}
return panel;
}
-
+
void changed() {
if (!changed) {
changed = true;
diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java
index 39c5c25ba6..e546e4d260 100644
--- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java
+++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,129 +18,89 @@
*/
package org.sleuthkit.autopsy.coordinationservice;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
-import org.apache.zookeeper.CreateMode;
-import org.apache.zookeeper.ZooDefs;
import org.apache.curator.RetryPolicy;
-import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
+import org.apache.curator.retry.ExponentialBackoffRetry;
+import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
-import org.sleuthkit.autopsy.core.UserPreferences;
-import java.io.IOException;
-import org.apache.zookeeper.WatchedEvent;
-import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.KeeperException.NoNodeException;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.ZooKeeper;
+import org.sleuthkit.autopsy.core.UserPreferences;
/**
- * A centralized service for maintaining configuration information and providing
- * distributed synchronization using a shared hierarchical namespace of nodes.
+ * A coordination service for maintaining configuration information and
+ * providing distributed synchronization using a shared hierarchical namespace
+ * of nodes.
+ *
+ * TODO (JIRA 2205): Simple refactoring for general use.
*/
public final class CoordinationService {
- /**
- * Category nodes are the immediate children of the root node of a shared
- * hierarchical namespace managed by the coordination service.
- */
- public enum CategoryNode { // RJCTODO: Move this to CoordinationServiceNamespace
-
- CASES("cases"),
- MANIFESTS("manifests"),
- CONFIG("config"),
- RESOURCE("resource");
-
- private final String displayName;
-
- private CategoryNode(String displayName) {
- this.displayName = displayName;
- }
-
- public String getDisplayName() {
- return displayName;
- }
- }
-
- /**
- * Exception type thrown by the coordination service.
- */
- public final static class CoordinationServiceException extends Exception {
-
- private static final long serialVersionUID = 1L;
-
- private CoordinationServiceException(String message) {
- super(message);
- }
-
- private CoordinationServiceException(String message, Throwable cause) {
- super(message, cause);
- }
- }
-
- /**
- * An opaque encapsulation of a lock for use in distributed synchronization.
- * Instances are obtained by calling a get lock method and must be passed to
- * a release lock method.
- */
- public static class Lock implements AutoCloseable {
-
- /**
- * This implementation uses the Curator read/write lock. see
- * http://curator.apache.org/curator-recipes/shared-reentrant-read-write-lock.html
- */
- private final InterProcessMutex interProcessLock;
- private final String nodePath;
-
- private Lock(String nodePath, InterProcessMutex lock) {
- this.nodePath = nodePath;
- this.interProcessLock = lock;
- }
-
- public String getNodePath() {
- return nodePath;
- }
-
- public void release() throws CoordinationServiceException {
- try {
- this.interProcessLock.release();
- } catch (Exception ex) {
- throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex);
- }
- }
-
- @Override
- public void close() throws CoordinationServiceException {
- release();
- }
- }
-
private static CuratorFramework curator = null;
private static final Map rootNodesToServices = new HashMap<>();
- private final Map categoryNodeToPath = new HashMap<>();
private static final int SESSION_TIMEOUT_MILLISECONDS = 300000;
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 300000;
private static final int ZOOKEEPER_SESSION_TIMEOUT_MILLIS = 3000;
private static final int ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS = 15000;
- private static final int PORT_OFFSET = 1000;
+ private static final int PORT_OFFSET = 1000; // When run in Solr, ZooKeeper defaults to Solr port + 1000
+ private final Map categoryNodeToPath = new HashMap<>();
/**
- * Gets an instance of the centralized coordination service for a specific
- * namespace.
+ * Determines if ZooKeeper is accessible with the current settings. Closes
+ * the connection prior to returning.
+ *
+ * @return true if a connection was achieved, false otherwise
+ *
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ private static boolean isZooKeeperAccessible() throws InterruptedException, IOException {
+ boolean result = false;
+ Object workerThreadWaitNotifyLock = new Object();
+ int zooKeeperServerPort = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET;
+ String connectString = UserPreferences.getIndexingServerHost() + ":" + zooKeeperServerPort;
+ ZooKeeper zooKeeper = new ZooKeeper(connectString, ZOOKEEPER_SESSION_TIMEOUT_MILLIS,
+ (WatchedEvent event) -> {
+ synchronized (workerThreadWaitNotifyLock) {
+ workerThreadWaitNotifyLock.notify();
+ }
+ });
+ synchronized (workerThreadWaitNotifyLock) {
+ workerThreadWaitNotifyLock.wait(ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS);
+ }
+ ZooKeeper.States state = zooKeeper.getState();
+ if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTEDREADONLY) {
+ result = true;
+ }
+ zooKeeper.close();
+ return result;
+ }
+
+ /**
+ * Gets a coordination service for a specific namespace.
*
* @param rootNode The name of the root node that defines the namespace.
*
- * @return The service for the namespace defined by the root node name.
+ * @return The coordination service.
*
- * @throws CoordinationServiceException If an instaNce of the coordination
+ * @throws CoordinationServiceException If an instance of the coordination
* service cannot be created.
*/
- public static synchronized CoordinationService getInstance(String rootNode) throws CoordinationServiceException {
+ public static synchronized CoordinationService getServiceForNamespace(String rootNode) throws CoordinationServiceException {
+ /*
+ * Connect to ZooKeeper via Curator.
+ */
if (null == curator) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
- // When run in Solr, ZooKeeper defaults to Solr port + 1000
int zooKeeperServerPort = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET;
String connectString = UserPreferences.getIndexingServerHost() + ":" + zooKeeperServerPort;
curator = CuratorFrameworkFactory.newClient(connectString, SESSION_TIMEOUT_MILLISECONDS, CONNECTION_TIMEOUT_MILLISECONDS, retryPolicy);
@@ -157,7 +117,7 @@ public final class CoordinationService {
CoordinationService service;
try {
service = new CoordinationService(rootNode);
- } catch (Exception ex) {
+ } catch (IOException | InterruptedException | KeeperException | CoordinationServiceException ex) {
curator = null;
throw new CoordinationServiceException("Failed to create coordination service", ex);
}
@@ -167,15 +127,18 @@ public final class CoordinationService {
}
/**
- * Constructs an instance of the centralized coordination service for a
- * specific namespace.
+ * Constructs an instance of the coordination service for a specific
+ * namespace.
*
* @param rootNodeName The name of the root node that defines the namespace.
+ *
+ * @throws Exception (calls Curator methods that throw Exception instead of
+ * more specific exceptions)
*/
- private CoordinationService(String rootNodeName) throws Exception {
+ private CoordinationService(String rootNodeName) throws InterruptedException, IOException, KeeperException, CoordinationServiceException {
if (false == isZooKeeperAccessible()) {
- throw new Exception("Unable to access ZooKeeper");
+ throw new CoordinationServiceException("Unable to access ZooKeeper");
}
String rootNode = rootNodeName;
@@ -191,6 +154,8 @@ public final class CoordinationService {
if (ex.code() != KeeperException.Code.NODEEXISTS) {
throw ex;
}
+ } catch (Exception ex) {
+ throw new CoordinationServiceException("Curator experienced an error", ex);
}
categoryNodeToPath.put(node.getDisplayName(), nodePath);
}
@@ -201,6 +166,9 @@ public final class CoordinationService {
* in the namespace managed by this coordination service. Blocks until the
* lock is obtained or the time out expires.
*
+ * IMPORTANT: The lock needs to be released in the same thread in which it
+ * is acquired.
+ *
* @param category The desired category in the namespace.
* @param nodePath The node path to use as the basis for the lock.
* @param timeOut Length of the time out.
@@ -236,6 +204,9 @@ public final class CoordinationService {
* in the namespace managed by this coordination service. Returns
* immediately if the lock can not be acquired.
*
+ * IMPORTANT: The lock needs to be released in the same thread in which it
+ * is acquired.
+ *
* @param category The desired category in the namespace.
* @param nodePath The node path to use as the basis for the lock.
*
@@ -262,6 +233,9 @@ public final class CoordinationService {
* the namespace managed by this coordination service. Blocks until the lock
* is obtained or the time out expires.
*
+ * IMPORTANT: The lock needs to be released in the same thread in which it
+ * is acquired.
+ *
* @param category The desired category in the namespace.
* @param nodePath The node path to use as the basis for the lock.
* @param timeOut Length of the time out.
@@ -297,6 +271,9 @@ public final class CoordinationService {
* the namespace managed by this coordination service. Returns immediately
* if the lock can not be acquired.
*
+ * IMPORTANT: The lock needs to be released in the same thread in which it
+ * is acquired.
+ *
* @param category The desired category in the namespace.
* @param nodePath The node path to use as the basis for the lock.
*
@@ -385,35 +362,77 @@ public final class CoordinationService {
}
/**
- * Determines if ZooKeeper is accessible with the current settings. Closes
- * the connection prior to returning.
- *
- * @return true if a connection was achieved, false otherwise
+ * Exception type thrown by the coordination service.
*/
- private static boolean isZooKeeperAccessible() {
- boolean result = false;
- Object workerThreadWaitNotifyLock = new Object();
- int zooKeeperServerPort = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET;
- String connectString = UserPreferences.getIndexingServerHost() + ":" + zooKeeperServerPort;
+ public final static class CoordinationServiceException extends Exception {
- try {
- ZooKeeper zooKeeper = new ZooKeeper(connectString, ZOOKEEPER_SESSION_TIMEOUT_MILLIS,
- (WatchedEvent event) -> {
+ private static final long serialVersionUID = 1L;
- synchronized (workerThreadWaitNotifyLock) {
- workerThreadWaitNotifyLock.notify();
- }
- });
- synchronized (workerThreadWaitNotifyLock) {
- workerThreadWaitNotifyLock.wait(ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS);
- }
- ZooKeeper.States state = zooKeeper.getState();
- if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTEDREADONLY) {
- result = true;
- }
- zooKeeper.close();
- } catch (InterruptedException | IOException ignored) {
+ private CoordinationServiceException(String message) {
+ super(message);
+ }
+
+ private CoordinationServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * An opaque encapsulation of a lock for use in distributed synchronization.
+ * Instances are obtained by calling a get lock method and must be passed to
+ * a release lock method.
+ */
+ public static class Lock implements AutoCloseable {
+
+ /**
+ * This implementation uses the Curator read/write lock. see
+ * http://curator.apache.org/curator-recipes/shared-reentrant-read-write-lock.html
+ */
+ private final InterProcessMutex interProcessLock;
+ private final String nodePath;
+
+ private Lock(String nodePath, InterProcessMutex lock) {
+ this.nodePath = nodePath;
+ this.interProcessLock = lock;
+ }
+
+ public String getNodePath() {
+ return nodePath;
+ }
+
+ public void release() throws CoordinationServiceException {
+ try {
+ this.interProcessLock.release();
+ } catch (Exception ex) {
+ throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex);
+ }
+ }
+
+ @Override
+ public void close() throws CoordinationServiceException {
+ release();
+ }
+ }
+
+ /**
+ * Category nodes are the immediate children of the root node of a shared
+ * hierarchical namespace managed by a coordination service.
+ */
+ public enum CategoryNode {
+
+ CASES("cases"),
+ MANIFESTS("manifests"),
+ CONFIG("config"),
+ RESOURCE("resource");
+
+ private final String displayName;
+
+ private CategoryNode(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
}
- return result;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java
index e1f2a3df42..567dd38bc6 100644
--- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java
+++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2016-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +19,7 @@
package org.sleuthkit.autopsy.coordinationservice;
/**
- * Namespace elements for auto ingest coordination service nodes.
+ * Root node for Autopsy coordination service namespace.
*/
public final class CoordinationServiceNamespace {
private static final String ROOT = "autopsy";
diff --git a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java
index 1ad74738dc..17999cc363 100644
--- a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java
+++ b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-2015 Basis Technology Corp.
+ * Copyright 2013-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,46 +24,70 @@ package org.sleuthkit.autopsy.core;
*/
public class RuntimeProperties {
- private static boolean coreComponentsActive = true;
- private static boolean coreComponentsActiveSet = false;
+ private static boolean runningWithGUI = true;
+ private static boolean runningWithGUIFlagHasBeenSet = 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.
+ * Sets or unsets a flag indicating whether or not the application is
+ * running with a GUI. The flag can only be set once per application
+ * innvocation.
*
- * @param coreComponentsActive True or false.
+ * @param runningWithGUI True or false.
+ *
+ * @throws RuntimePropertiesException if the flag has already been set.
*/
- public synchronized static void setCoreComponentsActive(boolean coreComponentsActive) {
- if (!coreComponentsActiveSet) {
- RuntimeProperties.coreComponentsActive = coreComponentsActive;
- coreComponentsActiveSet = true;
+ public synchronized static void setRunningWithGUI(boolean runningWithGUI) throws RuntimePropertiesException {
+ if (!runningWithGUIFlagHasBeenSet) {
+ RuntimeProperties.runningWithGUI = runningWithGUI;
+ runningWithGUIFlagHasBeenSet = true;
+ } else {
+ throw new RuntimePropertiesException("The runningWithGUI flag has already been set and cannot be changed");
}
}
/**
- * 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.
+ * Gets a flag indicating whether or not the application is running with a
+ * GUI.
*
* @return True or false.
*/
- public synchronized static boolean coreComponentsAreActive() {
- return coreComponentsActive;
+ public synchronized static boolean runningWithGUI() {
+ return runningWithGUI;
}
-
+
/**
* Private constructor to prevent creation of instances of this class.
*/
private RuntimeProperties() {
-
}
+
+ /**
+ * Exception to throw if there is an error setting a runtime property.
+ */
+ public final static class RuntimePropertiesException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor for an exception to throw if there is an error setting
+ * a runtime property.
+ *
+ * @param message The exception message.
+ */
+ public RuntimePropertiesException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor for an exception to throw if there is an error setting
+ * a runtime property.
+ *
+ * @param message The exception message.
+ * @param cause The cause of the error.
+ */
+ public RuntimePropertiesException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
index 5a169b7329..822d8bf95c 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
@@ -49,55 +49,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -111,6 +62,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -142,24 +117,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -264,73 +222,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
index 95d9419f7c..dbc6358fcc 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
@@ -18,13 +18,7 @@
*/
package org.sleuthkit.autopsy.corecomponents;
-import java.text.NumberFormat;
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JFormattedTextField;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
import org.netbeans.spi.options.OptionsPanelController;
-import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences;
/**
@@ -36,55 +30,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
AutopsyOptionsPanel() {
initComponents();
-
- /*
- * Profiling has shown that contention for I/O resources and for the
- * case database limits the number of threads that can do meaningful
- * work during ingest. If Autopsy was compute-bound, adding more threads
- * on machines with enough processors might help, but currently, after
- * four threads, performance actually stays flat and then starts to
- * degrade.
- */
- int availableProcessors = Runtime.getRuntime().availableProcessors();
- Integer fileIngestThreadCountChoices[];
- int recommendedFileIngestThreadCount;
- if (availableProcessors >= 6) {
- fileIngestThreadCountChoices = new Integer[]{1, 2, 4};
- recommendedFileIngestThreadCount = 4;
- } else if (availableProcessors >= 4 && availableProcessors < 6) {
- fileIngestThreadCountChoices = new Integer[]{1, 2, 4};
- recommendedFileIngestThreadCount = 2;
- } else if (availableProcessors >= 2 && availableProcessors < 4) {
- fileIngestThreadCountChoices = new Integer[]{1, 2};
- recommendedFileIngestThreadCount = 1;
- } else {
- fileIngestThreadCountChoices = new Integer[]{1};
- recommendedFileIngestThreadCount = 1;
- }
- numberOfFileIngestThreadsComboBox.setModel(new DefaultComboBoxModel<>(fileIngestThreadCountChoices));
-
- restartRequiredLabel.setText(NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.restartRequiredLabel.text", recommendedFileIngestThreadCount));
-
- // TODO listen to changes in form fields and call controller.changed()
- DocumentListener docListener = new DocumentListener() {
-
- @Override
- public void insertUpdate(DocumentEvent e) {
- firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
- }
-
- @Override
- public void removeUpdate(DocumentEvent e) {
- firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
- }
-
- @Override
- public void changedUpdate(DocumentEvent e) {
- firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
- }
- };
- this.jFormattedTextFieldProcTimeOutHrs.getDocument().addDocumentListener(docListener);
-
}
void load() {
@@ -98,20 +43,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
boolean useLocalTime = UserPreferences.displayTimesInLocalTime();
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() {
@@ -121,14 +52,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
UserPreferences.setHideSlackFilesInDataSourcesTree(dataSourcesHideSlackCB.isSelected());
UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCB.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() {
@@ -156,13 +79,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
jLabelHideKnownFiles = new javax.swing.JLabel();
dataSourcesHideKnownCB = new javax.swing.JCheckBox();
viewsHideKnownCB = new javax.swing.JCheckBox();
- 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());
dataSourcesHideSlackCB = new javax.swing.JCheckBox();
viewsHideSlackCB = new javax.swing.JCheckBox();
jLabelHideSlackFiles = new javax.swing.JLabel();
@@ -223,35 +139,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
}
});
- org.openide.awt.Mnemonics.setLocalizedText(jLabelNumThreads, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelNumThreads.text")); // NOI18N
-
- numberOfFileIngestThreadsComboBox.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- numberOfFileIngestThreadsComboBoxActionPerformed(evt);
- }
- });
-
- 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
- jFormattedTextFieldProcTimeOutHrs.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- jFormattedTextFieldProcTimeOutHrsActionPerformed(evt);
- }
- });
-
org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideSlackCB.text")); // NOI18N
dataSourcesHideSlackCB.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
@@ -277,29 +164,13 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jLabelTimeDisplay)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGap(10, 10, 10)
- .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(18, 18, 18)
- .addComponent(restartRequiredLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- .addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jLabelTimeDisplay)
- .addComponent(jLabelNumThreads)
- .addComponent(jLabelSetProcessTimeOut)
- .addGroup(jPanel1Layout.createSequentialGroup()
- .addGap(10, 10, 10)
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(useLocalTimeRB)
- .addComponent(useGMTTimeRB)
- .addGroup(jPanel1Layout.createSequentialGroup()
- .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)))))
- .addGap(213, 213, 213)))
- .addContainerGap())
+ .addComponent(useLocalTimeRB)
+ .addComponent(useGMTTimeRB))))
+ .addContainerGap(512, Short.MAX_VALUE))
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabelHideKnownFiles)
@@ -349,21 +220,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addComponent(useLocalTimeRB)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(useGMTTimeRB)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(jLabelNumThreads)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(jPanel1Layout.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))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(jLabelSetProcessTimeOut)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jCheckBoxEnableProcTimeout)
- .addGroup(jPanel1Layout.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(49, Short.MAX_VALUE))
+ .addContainerGap(148, Short.MAX_VALUE))
);
jScrollPane1.setViewportView(jPanel1);
@@ -380,11 +237,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
);
}// //GEN-END:initComponents
- private void jCheckBoxEnableProcTimeoutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxEnableProcTimeoutActionPerformed
- jFormattedTextFieldProcTimeOutHrs.setEditable(jCheckBoxEnableProcTimeout.isSelected());
- firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
- }//GEN-LAST:event_jCheckBoxEnableProcTimeoutActionPerformed
-
private void useBestViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRBActionPerformed
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}//GEN-LAST:event_useBestViewerRBActionPerformed
@@ -409,14 +261,6 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}//GEN-LAST:event_useGMTTimeRBActionPerformed
- private void numberOfFileIngestThreadsComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_numberOfFileIngestThreadsComboBoxActionPerformed
- firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
- }//GEN-LAST:event_numberOfFileIngestThreadsComboBoxActionPerformed
-
- private void jFormattedTextFieldProcTimeOutHrsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jFormattedTextFieldProcTimeOutHrsActionPerformed
- firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
- }//GEN-LAST:event_jFormattedTextFieldProcTimeOutHrsActionPerformed
-
private void dataSourcesHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCBActionPerformed
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}//GEN-LAST:event_dataSourcesHideSlackCBActionPerformed
@@ -430,20 +274,13 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
private javax.swing.ButtonGroup buttonGroup3;
private javax.swing.JCheckBox dataSourcesHideKnownCB;
private javax.swing.JCheckBox dataSourcesHideSlackCB;
- private javax.swing.JCheckBox jCheckBoxEnableProcTimeout;
- private javax.swing.JFormattedTextField jFormattedTextFieldProcTimeOutHrs;
private javax.swing.JLabel jLabelHideKnownFiles;
private javax.swing.JLabel jLabelHideSlackFiles;
- 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.JPanel jPanel1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JRadioButton keepCurrentViewerRB;
- private javax.swing.JComboBox numberOfFileIngestThreadsComboBox;
- private javax.swing.JLabel restartRequiredLabel;
private javax.swing.JRadioButton useBestViewerRB;
private javax.swing.JRadioButton useGMTTimeRB;
private javax.swing.JRadioButton useLocalTimeRB;
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 7f5cde3851..1aa7a3b24e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -1,7 +1,7 @@
CTL_DataContentAction=DataContent
CTL_DataContentTopComponent=Data Content
CTL_CustomAboutAction=About
-OptionsCategory_Name_General=Autopsy
+OptionsCategory_Name_General=View
OptionsCategory_Keywords_General=Autopsy Options
HINT_DataContentTopComponent=This is a DataContent window
HINT_NodeTableTopComponent=This is a DataResult window
@@ -139,11 +139,9 @@ AutopsyOptionsPanel.useGMTTimeRB.text=Use GMT
AutopsyOptionsPanel.useLocalTimeRB.text=Use local time zone
AutopsyOptionsPanel.keepCurrentViewerRB.toolTipText=For example, stay in Hex view when a JPEG is selected.
AutopsyOptionsPanel.keepCurrentViewerRB.text=Stay on the same file viewer
-AutopsyOptionsPanel.restartRequiredLabel.text=For this computer, a maximum of {0} file ingest threads should be used. Application restart required to take effect.
AutopsyOptionsPanel.jLabelSelectFile.text=When selecting a file:
AutopsyOptionsPanel.jLabelHideKnownFiles.text=Hide known files (i.e. those in the NIST NSRL) in the:
AutopsyOptionsPanel.jLabelTimeDisplay.text=When displaying times:
-AutopsyOptionsPanel.jLabelNumThreads.text=Number of threads to use for file ingest:
FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk
@@ -158,10 +156,6 @@ MultiUserSettingsPanel.validationErrMsg.invalidDatabasePort=Invalid database por
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}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties
index b5977466ba..46544c99b2 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties
@@ -1,5 +1,4 @@
CTL_DataContentAction=\u30c7\u30fc\u30bf\u30b3\u30f3\u30c6\u30f3\u30c4
-OptionsCategory_Name_General=Autopsy
OptionsCategory_Keywords_General=Autopsy\u30aa\u30d7\u30b7\u30e7\u30f3
CTL_CustomAboutAction=Autopsy\u306b\u3064\u3044\u3066
CTL_DataContentTopComponent=\u30c7\u30fc\u30bf\u30b3\u30f3\u30c6\u30f3\u30c4
@@ -116,11 +115,9 @@ AutopsyOptionsPanel.useGMTTimeRB.text=GMT\u3092\u4f7f\u7528
AutopsyOptionsPanel.useLocalTimeRB.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\u3092\u4f7f\u7528
AutopsyOptionsPanel.keepCurrentViewerRB.toolTipText=\u4f8b\u3048\u3070\u3001JPEG\u304c\u9078\u629e\u3055\u308c\u305f\u5834\u5408\u306b\u305d\u306e\u307e\u307eHEX\u30d3\u30e5\u30fc\u3092\u4f7f\u7528\u3002
AutopsyOptionsPanel.keepCurrentViewerRB.text=\u305d\u306e\u307e\u307e\u540c\u3058\u30d5\u30a1\u30a4\u30eb\u30d3\u30e5\u30fc\u30a2\u3092\u4f7f\u7528
-AutopsyOptionsPanel.restartRequiredLabel.text=\u3053\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u3067\u306f\u6700\u5927{0}\u306e\u30d5\u30a1\u30a4\u30eb\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b9\u30ec\u30c3\u30c9\u3092\u4f7f\u7528\u3059\u3079\u304d\u3067\u3059\u3002\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u518d\u8d77\u52d5\u304c\u5fc5\u8981\u3067\u3059\u3002
AutopsyOptionsPanel.jLabelSelectFile.text=\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e\u3059\u308b\u5834\u5408\uff1a
AutopsyOptionsPanel.jLabelHideKnownFiles.text=\u65e2\u77e5\u30d5\u30a1\u30a4\u30eb\uff08NIST NSRL\u5185\u306e\uff09\u3092\u6b21\u306b\u96a0\u3059\uff1a
AutopsyOptionsPanel.jLabelTimeDisplay.text=\u6642\u9593\u3092\u8868\u793a\u3059\u308b\u5834\u5408\uff1a
-AutopsyOptionsPanel.jLabelNumThreads.text=\u30d5\u30a1\u30a4\u30eb\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306b\u4f7f\u7528\u3059\u308b\u30b9\u30ec\u30c3\u30c9\u6570\uff1a
FXVideoPanel.progress.bufferingCancelled=\u30e1\u30c7\u30a3\u30a2\u306e\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\u304c\u30ad\u30e3\u30f3\u30bb\u30eb\u3055\u308c\u307e\u3057\u305f
FXVideoPanel.progress.bufferingInterrupted=\u30e1\u30c7\u30a3\u30a2\u306e\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\u304c\u4e2d\u65ad\u3055\u308c\u307e\u3057\u305f
FXVideoPanel.progress.errorWritingVideoToDisk=\u30d3\u30c7\u30aa\u3092\u30c7\u30a3\u30b9\u30af\u3078\u66f8\u304d\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
@@ -135,8 +132,6 @@ MultiUserSettingsPanel.validationErrMsg.invalidDatabasePort=\u7121\u52b9\u306a\u
MultiUserSettingsPanel.validationErrMsg.invalidMessageServicePort=\u7121\u52b9\u306a\u30e1\u30c3\u30bb\u30fc\u30b8\u30b5\u30fc\u30d3\u30b9\u30dd\u30fc\u30c8\u756a\u53f7
MultiUserSettingsPanel.validationErrMsg.invalidIndexingServerPort=\u7121\u52b9\u306aSolr\u30b5\u30fc\u30d0\u30fc\u30dd\u30fc\u30c8\u756a\u53f7
MultiUserSettingsPanel.validationErrMsg.invalidMessgeServiceURI=\u7121\u52b9\u306a\u30e1\u30c3\u30bb\u30fc\u30b8\u30b5\u30fc\u30d3\u30b9\u30db\u30b9\u30c8\u3084\u30dd\u30fc\u30c8\u756a\u53f7
-AutopsyOptionsPanel.jLabelProcessTimeOutUnits.text=\u6642\u9593
-AutopsyOptionsPanel.jLabelSetProcessTimeOut.text=\u4e00\u5b9a\u306e\u6642\u9593\u304c\u904e\u304e\u305f\u5f8c\u306b\u81ea\u52d5\u7684\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u505c\u6b62\u3067\u304d\u308b\u3088\u3046\u306b\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3092\u6709\u52b9\u5316\uff1a
DataContentViewerHex.goToOffsetLabel.text=\u30aa\u30d5\u30bb\u30c3\u30c8\u306b\u30b8\u30e3\u30f3\u30d7
DataContentViewerHex.goToOffsetTextField.msgDlg=\u7121\u52b9\u306a\u30aa\u30d5\u30bb\u30c3\u30c8\uff1a{0}
DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue=\u8a08\u7b97\u3055\u308c\u305f\u30aa\u30d5\u30bb\u30c3\u30c8\u306b\u30b8\u30e3\u30f3\u30d7\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java
index 83532c70ed..6f7c87ad0b 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,7 +22,6 @@ import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
-import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JTabbedPane;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
@@ -30,6 +29,7 @@ import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
+import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Top component that organizes all of the data content viewers. Doing a lookup
@@ -42,17 +42,18 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
//@TopComponent.OpenActionRegistration(displayName = "#CTL_DataContentAction", preferredID = "DataContentTopComponent")
public final class DataContentTopComponent extends TopComponent implements DataContent {
- private static Logger logger = Logger.getLogger(DataContentTopComponent.class.getName());
+ private static final Logger logger = Logger.getLogger(DataContentTopComponent.class.getName());
// reference to the "default" TC that always stays open
private static DataContentTopComponent defaultInstance;
+ private static final long serialVersionUID = 1L;
// set to true if this is the TC that always stays open and is the default place to display content
- private boolean isDefault;
+ private final boolean isDefault;
// the content panel holding tabs with content viewers
private final DataContentPanel dataContentPanel;
// contains a list of the undocked TCs
- private static ArrayList newWindowList = new ArrayList();
+ private static final ArrayList newWindowList = new ArrayList<>();
private static final String PREFERRED_ID = "DataContentTopComponent"; //NON-NLS
private static final String DEFAULT_NAME = NbBundle.getMessage(DataContentTopComponent.class, "CTL_DataContentTopComponent");
private static final String TOOLTIP_TEXT = NbBundle.getMessage(DataContentTopComponent.class, "HINT_DataContentTopComponent");
@@ -67,8 +68,8 @@ public final class DataContentTopComponent extends TopComponent implements DataC
dataContentPanel = new DataContentPanel(isDefault);
add(dataContentPanel);
- putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.valueOf(isDefault)); // prevent option to close compoment in GUI
- logger.log(Level.INFO, "Created DataContentTopComponent instance: " + this); //NON-NLS
+ putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isDefault); // prevent option to close compoment in GUI
+ logger.log(Level.INFO, "Created DataContentTopComponent instance: {0}", this); //NON-NLS
}
/**
@@ -91,24 +92,13 @@ public final class DataContentTopComponent extends TopComponent implements DataC
return dctc;
}
- /**
- * This method is called from within the constructor to initialize the form.
- * WARNING: Do NOT modify this code. The content of this method is always
- * regenerated by the Form Editor.
- */
- // //GEN-BEGIN:initComponents
- private void initComponents() {
-
- setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
- }// //GEN-END:initComponents
- // Variables declaration - do not modify//GEN-BEGIN:variables
- // End of variables declaration//GEN-END:variables
-
/**
* Gets default instance. Do not use directly: reserved for *.settings files
* only, i.e. deserialization routines; otherwise you could get a
* non-deserialized defaultInstance. To obtain the singleton instance, use
- * {@link #findInstance}.
+ * findInstance.
+ *
+ * @return
*/
public static synchronized DataContentTopComponent getDefault() {
if (defaultInstance == null) {
@@ -118,8 +108,10 @@ public final class DataContentTopComponent extends TopComponent implements DataC
}
/**
- * Obtain the default DataContentTopComponent defaultInstance. Never call
- * {@link #getDefault} directly!
+ * Obtain the default DataContentTopComponent default instance. Never call
+ * getDefault directly!
+ *
+ * @return The default DataContentTopComponent.
*/
public static synchronized DataContentTopComponent findInstance() {
TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
@@ -171,7 +163,12 @@ public final class DataContentTopComponent extends TopComponent implements DataC
@Override
public boolean canClose() {
- return (!this.isDefault) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case
+ /*
+ * If this is the main content viewers top component in the bottom of
+ * the main window, only it to be closed when there's no case opened or
+ * no data sources in the open case.
+ */
+ return (!this.isDefault) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false;
}
@Override
@@ -195,4 +192,18 @@ public final class DataContentTopComponent extends TopComponent implements DataC
public static List getNewWindowList() {
return newWindowList;
}
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
+ }// //GEN-END:initComponents
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java
index 4206dddc1d..fa655ee088 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,7 +55,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
private ExplorerManager explorerManager;
private ExplorerManagerNodeSelectionListener emNodeSelectionListener;
-
+
private Node rootNode;
// Different DataResultsViewers
@@ -276,7 +276,9 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
@Override
public void propertyChange(PropertyChangeEvent evt) {
- if (!Case.isCaseOpen()) {
+ try {
+ Case.getCurrentCase();
+ } catch (IllegalStateException ex) {
// Handle the in-between condition when case is being closed
// and legacy selection events are pumped.
return;
@@ -334,7 +336,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
explorerManager.removePropertyChangeListener(emNodeSelectionListener);
explorerManager = null;
}
-
+
// clear all set nodes
for (UpdateWrapper drv : this.viewers) {
drv.setNode(null);
@@ -443,7 +445,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
@Override
public List getViewers() {
- List ret = new ArrayList();
+ List ret = new ArrayList<>();
for (UpdateWrapper w : viewers) {
ret.add(w.getViewer());
}
@@ -452,7 +454,12 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
}
public boolean canClose() {
- return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case
+ /*
+ * If this is the main results panel in the main top component in the
+ * upper right of the main window, only allow it to be closed when
+ * there's no case opened or no data sources in the open case.
+ */
+ return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false;
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
index 7761afe04d..e99a2f8aa9 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -314,7 +314,12 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override
public boolean canClose() {
- return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case
+ /*
+ * If this is the results top component in the upper right of the main
+ * window, only allow it to be closed when there's no case opened or no
+ * data sources in the open case.
+ */
+ return (!this.isMain) || !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false;
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java
index 34a752a1e8..814e4bf654 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-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import javax.swing.BorderFactory;
+import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
@@ -32,7 +33,9 @@ import org.netbeans.spi.sendopts.OptionProcessor;
import org.netbeans.swing.tabcontrol.plaf.DefaultTabbedContainerUI;
import org.openide.modules.ModuleInstall;
import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.IngestRunningCheck;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
@@ -50,7 +53,7 @@ public class Installer extends ModuleInstall {
private static Installer instance;
public synchronized static Installer getDefault() {
- if (instance == null) {
+ if (null == instance) {
instance = new Installer();
}
return instance;
@@ -80,21 +83,18 @@ public class Installer extends ModuleInstall {
if (processor instanceof OpenFromArguments) {
OpenFromArguments argsProcessor = (OpenFromArguments) processor;
final String caseFile = argsProcessor.getDefaultArg();
- if (caseFile != null && !caseFile.equals("") && caseFile.endsWith(CaseMetadata.getFileExtension()) && new File(caseFile).exists()) { //NON-NLS
- new Thread(() -> {
- try {
- Case.open(caseFile);
- } catch (Exception ex) {
- logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseFile), ex); //NON-NLS
- }
- }).start();
+ if (caseFile != null && !caseFile.isEmpty() && caseFile.endsWith(CaseMetadata.getFileExtension()) && new File(caseFile).exists()) { //NON-NLS
+ try {
+ Case.openAsCurrentCase(caseFile);
+ } catch (CaseActionException ex) {
+ logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseFile), ex); //NON-NLS
+ }
return;
}
}
}
StartupWindowProvider.getInstance().open();
});
-
}
@Override
@@ -105,19 +105,10 @@ public class Installer extends ModuleInstall {
@Override
public void close() {
new Thread(() -> {
- String caseDirName = null;
try {
- if (Case.isCaseOpen()) {
- Case currentCase = Case.getCurrentCase();
- caseDirName = currentCase.getCaseDirectory();
- currentCase.closeCase();
- }
+ Case.closeCurrentCase();
} catch (CaseActionException ex) {
- logger.log(Level.SEVERE, String.format("Error closing case with case directory %s", (null != caseDirName ? caseDirName : "?")), ex); //NON-NLS
- } catch (IllegalStateException ignored) {
- /*
- * No current case. Case.isCaseOpen is not reliable.
- */
+ logger.log(Level.SEVERE, "Error closing current case", ex); //NON-NLS
}
}).start();
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java
index 3d7dabb7a0..f57bc27b9c 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-15 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -140,12 +140,12 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
private void showErrorNode(String errorMessage, AbstractFile file) {
final Button externalViewerButton = new Button(Bundle.MediaViewImagePanel_externalViewerButton_text(), new ImageView(EXTERNAL));
- externalViewerButton.setOnAction(actionEvent -> //fx ActionEvent
+ externalViewerButton.setOnAction(actionEvent
+ -> //fx ActionEvent
/*
* TODO: why is the name passed into the action constructor? it
* means we duplicate this string all over the place -jm
- */
- new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file))
+ */ new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file))
.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent
);
@@ -171,11 +171,10 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
}
readImageTask = ImageUtils.newReadImageTask(file);
readImageTask.setOnSucceeded(succeeded -> {
- //Note that all error conditions are allready logged in readImageTask.succeeded()
if (!Case.isCaseOpen()) {
/*
- * handle in-between condition when case is being closed and
- * an image was previously selected
+ * Handle the in-between condition when case is being closed
+ * and an image was previously selected
*
* NOTE: I think this is unnecessary -jm
*/
@@ -200,7 +199,7 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
readImageTask.setOnFailed(failed -> {
if (!Case.isCaseOpen()) {
/*
- * handle in-between condition when case is being closed and
+ * Handle in-between condition when case is being closed and
* an image was previously selected
*
* NOTE: I think this is unnecessary -jm
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanelController.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanelController.java
index 80b3f9bb58..5ae6410d33 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanelController.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanelController.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-2014 Basis Technology Corp.
+ * Copyright 2013-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,7 +31,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
@OptionsPanelController.TopLevelRegistration(categoryName = "#OptionsCategory_Name_Multi_User_Settings",
iconBase = "org/sleuthkit/autopsy/images/User-Group-icon-green32.png",
- position = 2,
+ position = 3,
keywords = "#OptionsCategory_Keywords_Multi_User_Options",
keywordsCategory = "Multi-user")
public final class MultiUserSettingsPanelController extends OptionsPanelController {
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java
index 9c04a003ff..92fc5ce13e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.corecomponents;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
+import org.openide.util.lookup.Lookups;
/**
* A filter node that creates at most one layer of child nodes for the node it
@@ -44,7 +45,7 @@ public class TableFilterNode extends FilterNode {
* The constructor should include column order key. (See getColumnOrderKey)
*/
public TableFilterNode(Node wrappedNode, boolean createChildren) {
- super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren));
+ super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren) , Lookups.proxy(wrappedNode));
this.createChildren = createChildren;
}
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
index 0507e55cd6..753ccd598a 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
- *
+ *
* Copyright 2011-2016 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.
@@ -163,12 +163,22 @@ public final class ExecUtil {
process.waitFor(timeOut, units);
if (process.isAlive() && terminator.shouldTerminateProcess()) {
killProcess(process);
+ try {
+ process.waitFor(); //waiting to help ensure process is shutdown before calling interrupt() or returning
+ } catch (InterruptedException exx) {
+ Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, String.format("Wait for process termination following killProcess was interrupted for command %s", processBuilder.command().get(0)));
+ }
}
} while (process.isAlive());
} catch (InterruptedException ex) {
if (process.isAlive()) {
killProcess(process);
}
+ try {
+ process.waitFor(); //waiting to help ensure process is shutdown before calling interrupt() or returning
+ } catch (InterruptedException exx) {
+ Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, String.format("Wait for process termination following killProcess was interrupted for command %s", processBuilder.command().get(0)));
+ }
Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, "Thread interrupted while running {0}", processBuilder.command().get(0)); // NON-NLS
Thread.currentThread().interrupt();
}
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java b/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java
index b00f4cc513..02dc9fa112 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java
@@ -315,4 +315,29 @@ public class UNCPathUtilities {
}
return driveMap;
}
+
+ /**
+ * Converts a path to UNC, if possible. This is accomplished by checking the
+ * mapped drives list the operating system maintains and substituting where
+ * required. If the drive of the path passed in does not exist in the cached
+ * mapped drives list, a rescan of the mapped drives list is forced, and
+ * mapping is attempted one more time.
+ *
+ * @param indexDir the String of the absolute path to be converted to UNC,
+ * if possible
+ *
+ * @return UNC path if able to convert to UNC, original input path otherwise
+ */
+ synchronized public String convertPathToUNC(String indexDir) {
+ // if we can check for UNC paths, do so, otherwise just return the indexDir
+ String result = mappedDriveToUNC(indexDir);
+ if (result == null) {
+ rescanDrives();
+ result = mappedDriveToUNC(indexDir);
+ }
+ if (result == null) {
+ return indexDir;
+ }
+ return result;
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java
index 5df9f2756f..7c28e09529 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,62 +19,105 @@
package org.sleuthkit.autopsy.datamodel;
import java.text.SimpleDateFormat;
-import java.util.TimeZone;
+import java.util.Arrays;
import java.util.logging.Level;
-
+import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.datamodel.TskException;
/**
- * StringContent object for a blackboard artifact, that can be looked up and
- * used to display text for the DataContent viewers. Displays values in artifact
- * in HTML. Note that it has no style associated with it and assumes that the
- * pane showing the HTML has styles set (such as with HTMLEditorKit).
+ * An HTML representation of an artifact. The representation is plain vanilla
+ * HTML, so any styling needs to be supplied by the display mechansim. For
+ * example, GUI components such as content viewers might use HTMLEditorKit to
+ * add styling.
*/
public class ArtifactStringContent implements StringContent {
- BlackboardArtifact artifact;
+ private final static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private final static Logger logger = Logger.getLogger(ArtifactStringContent.class.getName());
+ private final BlackboardArtifact artifact;
private String stringContent = "";
- static final Logger logger = Logger.getLogger(ArtifactStringContent.class.getName());
- private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- public ArtifactStringContent(BlackboardArtifact art) {
- artifact = art;
+ /**
+ * Constructs an HTML representation of an artifact. The representation is
+ * plain vanilla HTML, so any styling needs to be supplied by the display
+ * mechansim. For example, GUI components such as content viewers might use
+ * HTMLEditorKit to add styling.
+ *
+ * @param artifact The artifact to be represented as HTML.
+ */
+ public ArtifactStringContent(BlackboardArtifact artifact) {
+ this.artifact = artifact;
}
+ /**
+ * Gets the HTML representation of the artifact.
+ *
+ * @return The HTML representation of the artifact as a string.
+ */
+ @Messages({
+ "ArtifactStringContent.attrsTableHeader.attribute=Attribute",
+ "ArtifactStringContent.attrsTableHeader.value=Value",
+ "ArtifactStringContent.attrsTableHeader.sources=Source(s)",
+ "ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database",
+ "ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database"
+ })
@Override
- @SuppressWarnings("deprecation")
public String getString() {
if (stringContent.isEmpty()) {
+ /*
+ * Start the document.
+ */
+ StringBuilder buffer = new StringBuilder(1024);
+ buffer.append("\n"); //NON-NLS
+ buffer.append("\n"); //NON-NLS
+
+ /*
+ * Use the artifact display name as a header.
+ */
+ buffer.append("
\n"); //NON-NLS
-
- // cycle through each attribute and display in a row in the table.
+ /*
+ * Add rows for each attribute.
+ */
for (BlackboardAttribute attr : artifact.getAttributes()) {
- // name column
+ /*
+ * Attribute display name column.
+ */
buffer.append("
"); //NON-NLS
buffer.append("\n"); //NON-NLS
-
stringContent = buffer.toString();
- } catch (TskException ex) {
- stringContent = NbBundle.getMessage(this.getClass(), "ArtifactStringContent.getStr.err");
}
}
return stringContent;
}
- private static Content getAssociatedContent(BlackboardArtifact artifact) {
- try {
- return artifact.getSleuthkitCase().getContentById(artifact.getObjectID());
- } catch (TskException ex) {
- logger.log(Level.WARNING, "Getting file failed", ex); //NON-NLS
- }
- throw new IllegalArgumentException(NbBundle.getMessage(ArtifactStringContent.class, "ArtifactStringContent.exception.msg"));
- }
-
- private static TimeZone getTimeZone(BlackboardArtifact artifact) {
- return ContentUtils.getTimeZone(getAssociatedContent(artifact));
-
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
index 7d15bb2101..dc6f5ab2ab 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -404,7 +404,8 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
if (attributeTypeID == ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()
|| attributeTypeID == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID()
|| attributeTypeID == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()
- || attributeTypeID == ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
+ || attributeTypeID == ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()
+ || attributeTypeID == ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID()) {
} else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) {
map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated));
} else if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getTypeID()
@@ -452,13 +453,6 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
forLookup.add(content);
}
- // if there is a text highlighted version, of the content, add it too
- // currently happens from keyword search module
- TextMarkupLookup highlight = getHighlightLookup(artifact, content);
- if (highlight != null) {
- forLookup.add(highlight);
- }
-
return Lookups.fixed(forLookup.toArray(new Object[forLookup.size()]));
}
@@ -472,39 +466,6 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.getAssocCont.exception.msg"));
}
- private static TextMarkupLookup getHighlightLookup(BlackboardArtifact artifact, Content content) {
- if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
- return null;
- }
-
- long objectId = content.getId();
-
- Lookup lookup = Lookup.getDefault();
- TextMarkupLookup highlightFactory = lookup.lookup(TextMarkupLookup.class);
- try {
- List attributes = artifact.getAttributes();
- String keyword = null;
- String regexp = null;
- for (BlackboardAttribute att : attributes) {
- final int attributeTypeID = att.getAttributeType().getTypeID();
- if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) {
- keyword = att.getValueString();
- } else if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()) {
- regexp = att.getValueString();
- } else if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
- objectId = att.getValueLong();
- }
- }
- if (keyword != null) {
- boolean isRegexp = StringUtils.isNotBlank(regexp);
- String origQuery = isRegexp ? regexp : keyword;
- return highlightFactory.createInstance(objectId, keyword, isRegexp, origQuery);
- }
- } catch (TskCoreException ex) {
- LOGGER.log(Level.WARNING, "Failed to retrieve Blackboard Attributes", ex); //NON-NLS
- }
- return null;
- }
@Override
public boolean isLeafTypeNode() {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
index cbe816ffe7..d178b73469 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
@@ -12,7 +12,7 @@ AbstractAbstractFileNode.modeColLbl=Mode
AbstractAbstractFileNode.useridColLbl=UserID
AbstractAbstractFileNode.groupidColLbl=GroupID
AbstractAbstractFileNode.metaAddrColLbl=Meta Addr.
-AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.
+AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.ArtifactStringContent.getStr.err
AbstractAbstractFileNode.typeDirColLbl=Type(Dir)
AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)
AbstractAbstractFileNode.knownColLbl=Known
@@ -25,7 +25,6 @@ AbstractContentNode.exception.cannotChangeSysName.msg=Cannot change the system n
AbstractFsContentNode.noDesc.text=no description
ArtifactStringContent.getStr.srcFilePath.text=Source File Path
ArtifactStringContent.getStr.err=Error getting content
-ArtifactStringContent.exception.msg=Could not get file from database
ArtifactTypeNode.createSheet.artType.name=Artifact Type
ArtifactTypeNode.createSheet.artType.displayName=Artifact Type
ArtifactTypeNode.createSheet.artType.desc=no description
@@ -172,8 +171,6 @@ KeywordHits.createNodeForKey.chgTime.desc=Change Time
KeywordHits.createNodeForKey.chgTime.name=ChangeTime
KeywordHits.createNodeForKey.accessTime.name=AccessTime
KeywordHits.createNodeForKey.modTime.name=ModifiedTime
-KnownFileFilterNode.selectionContext.dataSources=Data Sources
-KnownFileFilterNode.selectionContext.views=Views
LayoutFileNode.propertyType.parts=Parts
LayoutFileNode.createSheet.name.name=Name
LayoutFileNode.createSheet.name.displayName=Name
@@ -222,8 +219,6 @@ ReportNode.reportNameProperty.name=Report Name
ReportNode.reportNameProperty.displayName=Report Name
ReportNode.reportNameProperty.desc=Name of the report
ReportsListNode.displayName=Reports
-SlackFileFilterNode.selectionContext.dataSources=Data Sources
-SlackFileFilterNode.selectionContext.views=Views
SlackFileNode.getActions.viewInNewWin.text=View in New Window
SlackFileNode.getActions.viewFileInDir.text=View File in Directory
TagNameNode.namePlusTags.text={0} Tags
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties
index 4dfb781a45..56c4dd7bd5 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties
@@ -18,7 +18,6 @@ AbstractContentNode.exception.cannotChangeSysName.msg=\u30b7\u30b9\u30c6\u30e0\u
AbstractFsContentNode.noDesc.text=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093
ArtifactStringContent.getStr.srcFilePath.text=\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9
ArtifactStringContent.getStr.err=\u30b3\u30f3\u30c6\u30f3\u30c4\u53d6\u5f97\u30a8\u30e9\u30fc
-ArtifactStringContent.exception.msg=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304b\u3089\u30d5\u30a1\u30a4\u30eb\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
ArtifactTypeNode.createSheet.artType.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093
ArtifactTypeNode.createSheet.childCnt.name=\u30c1\u30e3\u30a4\u30eb\u30c9\u6570
ArtifactTypeNode.createSheet.childCnt.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
index cbe842ec70..7a040847ef 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
@@ -147,7 +147,9 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
final String mime_type = resultSet.getString("mime_type"); //NON-NLS
if (!mime_type.isEmpty()) {
String mimeType[] = mime_type.split("/");
- if (!mimeType[0].isEmpty() && !mimeType[1].isEmpty()) {
+ //Note: Users are able to define custom mime types in Autopsy that do not
+ //contain a "/" or possibly have multiple slashes
+ if (mimeType.length > 1 && !mimeType[0].isEmpty() && !mimeType[1].isEmpty()) {
if (!existingMimeTypes.containsKey(mimeType[0])) {
existingMimeTypes.put(mimeType[0], new ArrayList<>());
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
index 8b2f3b9ae7..682a3fa225 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.datamodel;
-import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.ResultSet;
@@ -27,7 +26,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
-import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
@@ -40,7 +38,7 @@ import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
-import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
@@ -101,22 +99,14 @@ public class ImageNode extends AbstractContentNode {
"ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",})
public Action[] getActions(boolean context) {
- List actionsList = new ArrayList();
+ List actionsList = new ArrayList<>();
for (Action a : super.getActions(true)) {
actionsList.add(a);
}
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
actionsList.add(new FileSearchAction(
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
- actionsList.add(new AbstractAction(
- Bundle.ImageNode_action_runIngestMods_text()) {
- @Override
- public void actionPerformed(ActionEvent e) {
- final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.singletonList(content));
- ingestDialog.display();
- }
- });
-
+ actionsList.add(new RunIngestModulesAction(Collections.singletonList(content)));
actionsList.add(new NewWindowViewAction(
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
return actionsList.toArray(new Action[0]);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KnownFileFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/KnownFileFilterNode.java
deleted file mode 100755
index 8a6dc86f3f..0000000000
--- a/Core/src/org/sleuthkit/autopsy/datamodel/KnownFileFilterNode.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2013-2014 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.datamodel;
-
-import java.util.prefs.PreferenceChangeEvent;
-import java.util.prefs.PreferenceChangeListener;
-import org.openide.nodes.FilterNode;
-import org.openide.nodes.Node;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.core.UserPreferences;
-import org.sleuthkit.datamodel.AbstractFile;
-import org.sleuthkit.datamodel.TskData;
-
-/**
- * A Filter Node responsible for conditionally filtering out Nodes that
- * represent known files.
- *
- * Filters known files IF the option to Filter Known files for the given
- * SelectionContext is set. Otherwise, does nothing.
- *
- * @author jwallace
- */
-public class KnownFileFilterNode extends FilterNode {
-
- private static boolean filterFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
- private static boolean filterFromViews = UserPreferences.hideKnownFilesInViewsTree();
-
- static {
- UserPreferences.addChangeListener(new PreferenceChangeListener() {
- @Override
- public void preferenceChange(PreferenceChangeEvent evt) {
- switch (evt.getKey()) {
- case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE:
- filterFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
- break;
- case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE:
- filterFromViews = UserPreferences.hideKnownFilesInViewsTree();
- break;
- }
- }
- });
- }
-
- public enum SelectionContext {
-
- DATA_SOURCES(NbBundle.getMessage(KnownFileFilterNode.class, "KnownFileFilterNode.selectionContext.dataSources")),
- VIEWS(NbBundle.getMessage(KnownFileFilterNode.class, "KnownFileFilterNode.selectionContext.views")),
- OTHER(""); // Subnode of another node.
-
- private final String displayName;
-
- SelectionContext(String displayName) {
- this.displayName = displayName;
- }
-
- public static SelectionContext getContextFromName(String name) {
- if (name.equals(DATA_SOURCES.getName())) {
- return DATA_SOURCES;
- } else if (name.equals(VIEWS.getName())) {
- return VIEWS;
- } else {
- return OTHER;
- }
- }
-
- private String getName() {
- return displayName;
- }
- }
-
- /**
- * Create a KnownFileFilterNode from the given Node. Note that the Node
- * should be from the directory tree.
- *
- * @param arg
- * @param context
- */
- public KnownFileFilterNode(Node arg, SelectionContext context) {
- super(arg, new KnownFileFilterChildren(arg, context));
- }
-
- private KnownFileFilterNode(Node arg, boolean filter) {
- super(arg, new KnownFileFilterChildren(arg, filter));
- }
-
- /**
- * Get the selection context of a Node in the DirectoryTree.
- *
- * @param n
- *
- * @return
- */
- public static SelectionContext getSelectionContext(Node n) {
- if (n == null || n.getParentNode() == null) {
- // Parent of root node or root node. Occurs during case open / close.
- return SelectionContext.OTHER;
- } else if (n.getParentNode().getParentNode() == null) {
- // One level below root node. Should be one of DataSources, Views, or Results
- return SelectionContext.getContextFromName(n.getDisplayName());
- } else {
- return getSelectionContext(n.getParentNode());
- }
- }
-
- /**
- * Complementary class to KnownFileFilterNode.
- *
- * Filters out children Nodes that represent known files. Otherwise, returns
- * the original node wrapped in another instance of the KnownFileFilterNode.
- *
- * @author jwallace
- */
- private static class KnownFileFilterChildren extends FilterNode.Children {
-
- /**
- * True if this KnownFileFilterChildren should filter out known files.
- */
- private boolean filter;
-
- /**
- * Constructor used when the context has already been determined.
- *
- * @param arg
- * @param filter
- */
- private KnownFileFilterChildren(Node arg, boolean filter) {
- super(arg);
- this.filter = filter;
- }
-
- /**
- * Constructor used when the context has not been determined.
- *
- * @param arg
- * @param context
- */
- private KnownFileFilterChildren(Node arg, KnownFileFilterNode.SelectionContext context) {
- super(arg);
-
- switch (context) {
- case DATA_SOURCES:
- filter = filterFromDataSources;
- break;
- case VIEWS:
- filter = filterFromViews;
- break;
- default:
- filter = false;
- break;
- }
- }
-
- @Override
- protected Node[] createNodes(Node arg) {
- if (filter) {
- // Filter out child nodes that represent known files
- AbstractFile file = arg.getLookup().lookup(AbstractFile.class);
- if (file != null && (file.getKnown() == TskData.FileKnown.KNOWN)) {
- return new Node[]{};
- }
- }
- return new Node[]{new KnownFileFilterNode(arg, filter)};
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileFilterNode.java
deleted file mode 100644
index af43970e8c..0000000000
--- a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileFilterNode.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2013-2014 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.datamodel;
-
-import java.util.prefs.PreferenceChangeEvent;
-import java.util.prefs.PreferenceChangeListener;
-import org.openide.nodes.FilterNode;
-import org.openide.nodes.Node;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.core.UserPreferences;
-import org.sleuthkit.datamodel.AbstractFile;
-import org.sleuthkit.datamodel.TskData;
-
-/**
- * A Filter Node responsible for conditionally filtering out Nodes that
- * represent slack files.
- *
- * Filters known files IF the option to Filter Slack files for the given
- * SelectionContext is set. Otherwise, does nothing.
- *
- */
-public class SlackFileFilterNode extends FilterNode {
-
- private static boolean filterFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
- private static boolean filterFromViews = UserPreferences.hideSlackFilesInViewsTree();
-
- static {
- UserPreferences.addChangeListener(new PreferenceChangeListener() {
- @Override
- public void preferenceChange(PreferenceChangeEvent evt) {
- switch (evt.getKey()) {
- case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE:
- filterFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
- break;
- case UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE:
- filterFromViews = UserPreferences.hideSlackFilesInViewsTree();
- break;
- }
- }
- });
- }
-
- public enum SelectionContext {
-
- DATA_SOURCES(NbBundle.getMessage(SlackFileFilterNode.class, "SlackFileFilterNode.selectionContext.dataSources")),
- VIEWS(NbBundle.getMessage(SlackFileFilterNode.class, "SlackFileFilterNode.selectionContext.views")),
- OTHER(""); // Subnode of another node.
-
- private final String displayName;
-
- SelectionContext(String displayName) {
- this.displayName = displayName;
- }
-
- public static SelectionContext getContextFromName(String name) {
- if (name.equals(DATA_SOURCES.getName())) {
- return DATA_SOURCES;
- } else if (name.equals(VIEWS.getName())) {
- return VIEWS;
- } else {
- return OTHER;
- }
- }
-
- private String getName() {
- return displayName;
- }
- }
-
- /**
- * Create a SlackFileFilterNode from the given Node. Note that the Node
- * should be from the directory tree.
- *
- * @param arg
- * @param context
- */
- public SlackFileFilterNode(Node arg, SelectionContext context) {
- super(arg, new SlackFileFilterChildren(arg, context));
- }
-
- private SlackFileFilterNode(Node arg, boolean filter) {
- super(arg, new SlackFileFilterChildren(arg, filter));
- }
-
- /**
- * Get the selection context of a Node in the DirectoryTree.
- *
- * @param n
- *
- * @return
- */
- public static SelectionContext getSelectionContext(Node n) {
- if (n == null || n.getParentNode() == null) {
- // Parent of root node or root node. Occurs during case open / close.
- return SelectionContext.OTHER;
- } else if (n.getParentNode().getParentNode() == null) {
- // One level below root node. Should be one of DataSources, Views, or Results
- return SelectionContext.getContextFromName(n.getDisplayName());
- } else {
- return getSelectionContext(n.getParentNode());
- }
- }
-
- /**
- * Complementary class to SlackFileFilterNode.
- *
- * Filters out children Nodes that represent slack files. Otherwise, returns
- * the original node wrapped in another instance of the SlackFileFilterNode.
- *
- * @author jwallace
- */
- private static class SlackFileFilterChildren extends FilterNode.Children {
-
- /**
- * True if this SlackFileFilterChildren should filter out slack files.
- */
- private boolean filter;
-
- /**
- * Constructor used when the context has already been determined.
- *
- * @param arg
- * @param filter
- */
- private SlackFileFilterChildren(Node arg, boolean filter) {
- super(arg);
- this.filter = filter;
- }
-
- /**
- * Constructor used when the context has not been determined.
- *
- * @param arg
- * @param context
- */
- private SlackFileFilterChildren(Node arg, SlackFileFilterNode.SelectionContext context) {
- super(arg);
-
- switch (context) {
- case DATA_SOURCES:
- filter = filterFromDataSources;
- break;
- case VIEWS:
- filter = filterFromViews;
- break;
- default:
- filter = false;
- break;
- }
- }
-
- @Override
- protected Node[] createNodes(Node arg) {
- if (filter) {
- // Filter out child nodes that represent slack files
- AbstractFile file = arg.getLookup().lookup(AbstractFile.class);
- if ((file != null) && file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
- return new Node[]{};
- }
- }
- return new Node[]{new SlackFileFilterNode(arg, filter)};
- }
- }
-}
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TextMarkupLookup.java b/Core/src/org/sleuthkit/autopsy/datamodel/TextMarkupLookup.java
deleted file mode 100644
index a2eef74396..0000000000
--- a/Core/src/org/sleuthkit/autopsy/datamodel/TextMarkupLookup.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.datamodel;
-
-/**
- * This interface acts as a sort of bridge between the Autopsy Core NetBeans
- * Module (NBM) and the Autopsy KeywordSearch NBM. It is used to get indexed
- * text marked up with HTML to highlight search hits for a particular keyword.
- *
- * Here is an example of how it works. It is used to put highlighted markup into
- * the Lookups of the BlackboardArtifactNodes for keyword search hit artifacts.
- * The BlackboardArtifactNode code that populates the node's Lookup asks the
- * default global Lookup for an instance of TextMarkupLookup. The
- * org.sleuthkit.autopsy.keywordsearch.HighlightedText class is the sole
- * implementation of the interface, so the BlackboardArtifactNode gets a default
- * constructed instance of HighlightedText. This otherwise useless
- * instance is then used to call createInstance with parameters that are used to
- * employ the Solr highlighting capability to create the markup. The
- * TextMarkupLookup object goes in the BlackboardArtifactNode Lookup for later
- * use by the ExtractedContentViewer, a DataContentViewer in the KeywordSearch
- * NBM.
- */
-public interface TextMarkupLookup {
-
- /**
- * Factory method for getting an object that encapsulates indexed text
- * marked up (HTML) to highlight search hits for a particular keyword.
- *
- * @param objectId ID of the object (file or artifact) that is the
- * source of the indexed text.
- * @param keyword The keyword to be highlighted in the text.
- * @param isRegex Whether or not the query that follows is a regex.
- * @param originalQuery The query that produces the keyword hit.
- *
- * @return An object that encapsulates indexed text marked up (HTML) to
- * highlight search hits for a particular keyword.
- */
- public TextMarkupLookup createInstance(long objectId, String keyword, boolean isRegex, String originalQuery);
-}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java
index 246c6d6e1d..8a056b2318 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
- *
- * Copyright 2011-2014 Basis Technology Corp.
+ *
+ * Copyright 2011-2017 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.
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.datamodel;
-import java.awt.event.ActionEvent;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -27,7 +26,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
-import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
@@ -38,7 +36,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
-import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@@ -50,7 +48,7 @@ import org.sleuthkit.datamodel.VirtualDirectory;
*/
public class VirtualDirectoryNode extends AbstractAbstractFileNode {
- private static Logger logger = Logger.getLogger(VirtualDirectoryNode.class.getName());
+ private static final Logger logger = Logger.getLogger(VirtualDirectoryNode.class.getName());
//prefix for special VirtualDirectory root nodes grouping local files
public final static String LOGICAL_FILE_SET_PREFIX = "LogicalFileSet"; //NON-NLS
@@ -100,14 +98,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNodesingletonList(content));
- ingestDialog.display();
- }
- });
+ actions.add(new RunIngestModulesAction(Collections.singletonList(content)));
actions.addAll(ContextMenuExtensionPoint.getActions());
return actions.toArray(new Action[0]);
}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java
index efddf47398..7201344100 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java
@@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.datasourceprocessors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
-import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
@@ -41,198 +41,218 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
* The ImageWriter class is used to complete VHD copies created from local disks
- * after the ingest process completes.
+ * after the ingest process completes. The AddImageTask for this data source must have included
+ * a non-empty imageWriterPath parameter to enable Image Writer.
*/
-public class ImageWriter {
+public class ImageWriter implements PropertyChangeListener{
private final Logger logger = Logger.getLogger(ImageWriter.class.getName());
- private final HashSet dataSourceIds = new HashSet<>();
- private final Object dataSourceIdsLock; // Get this lock before accessing dataSourceIds
+ private final Long dataSourceId;
- private final HashSet> progressUpdaters = new HashSet<>();
- private final HashSet imagesBeingFinished = new HashSet<>();
- private final HashSet progressBars = new HashSet<>();
- private final HashSet> finishTasksInProgress = new HashSet<>();
- private boolean isCancelled;
- private final Object currentTasksLock; // Get this lock before accessing imagesBeingFinished, progressBars, progressUpdaters, finishTasksInProgress or isCancelled
+ private Long imageHandle = null;
+ private Future> finishTask = null;
+ private ProgressHandle progressHandle = null;
+ private ScheduledFuture> progressUpdateTask = null;
+ private boolean isCancelled = false;
+ private boolean isStarted = false;
+ private boolean isFinished = false;
+ private final Object currentTasksLock = new Object(); // Get this lock before accessing imageHandle, finishTask, progressHandle, progressUpdateTask,
+ // isCancelled, isStarted, or isFinished
- private boolean listenerStarted;
private ScheduledThreadPoolExecutor periodicTasksExecutor = null;
private final boolean doUI;
- public ImageWriter(){
- dataSourceIdsLock = new Object();
- currentTasksLock = new Object();
- listenerStarted = false;
- isCancelled = false;
-
- doUI = RuntimeProperties.coreComponentsAreActive();
- if(doUI){
- periodicTasksExecutor = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS
- }
+ private static final AtomicInteger numFinishJobsInProgress = new AtomicInteger(0);
+
+ /**
+ * Create the Image Writer object.
+ * After creation, startListeners() should be called.
+ * @param dataSourceId
+ */
+ public ImageWriter(Long dataSourceId){
+ this.dataSourceId = dataSourceId;
+ doUI = RuntimeProperties.runningWithGUI();
}
/**
- * Creates a listener on IngestJobEvents if it hasn't already been started.
- * When a DataSourceAnalysisCompletedEvent arrives, if it matches
- * the data source ID of an image that is using Image Writer, then finish the image
- * (fill in any gaps). The AddImageTask for this data source must have included
- * a non-empty imageWriterPath parameter to enable Image Writer.
+ * Add this ImageWriter object as a listener to the necessary events
*/
- private synchronized void startListener(){
- if(! listenerStarted){
- IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() {
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){
- DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt;
+ public void subscribeToEvents(){
+ IngestManager.getInstance().addIngestJobEventListener(this);
+ Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this);
+ }
+
+ /**
+ * Deregister this object from the events. This is ok to call multiple times.
+ */
+ public void unsubscribeFromEvents(){
+ IngestManager.getInstance().removeIngestJobEventListener(this);
+ Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this);
+ }
+
+ /**
+ * Handle the events:
+ * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete
+ * CURRENT_CASE (case closing) - cancel the finish image process (if necessary)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if(evt instanceof DataSourceAnalysisCompletedEvent){
+
+ DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt;
- if(event.getDataSource() != null){
- long imageId = event.getDataSource().getId();
- String name = event.getDataSource().getName();
+ if(event.getDataSource() != null){
+ long imageId = event.getDataSource().getId();
+ String name = event.getDataSource().getName();
+
+ // Check that the event corresponds to this datasource
+ if(imageId != dataSourceId){
+ return;
+ }
+ new Thread(() -> {
+ startFinishImage(name);
+ }).start();
- // Check whether we need to run finishImage for this data source
- synchronized(dataSourceIdsLock){
- if( ! ImageWriter.this.dataSourceIds.contains(imageId)){
- // Image writer was not used on this data source or we've already finished it
- return;
- } else {
- // Remove the imageId from the list here so we can't get past this point twice
- // for the same image. Multiple DataSourceAnalysisCompletedEvent events can come from
- // the same image if more ingest modules are run later, but the imageId is only added
- // to the list during the intial task to add the image to the database.
- ImageWriter.this.dataSourceIds.remove(imageId);
- }
- }
- logger.log(Level.INFO, String.format("Finishing VHD image for %s",
- event.getDataSource().getName())); //NON-NLS
+ } else {
+ logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS
+ }
+ }
+ else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){
+ // Technically this could be a case open event (and not the expected case close event) but
+ // that would probably mean something bad is going on, and we'd still want to cancel
+ // Image Writer.
+ close();
+ }
+ }
+
+ private void startFinishImage(String dataSourceName){
+
+ synchronized(currentTasksLock){
+ if(isCancelled){
+ return;
+ }
+
+ // If we've already started the finish process for this datasource, return.
+ // Multiple DataSourceAnalysisCompletedEvent events can come from
+ // the same image if more ingest modules are run later
+ if(isStarted){
+ return;
+ } else {
+ isStarted = true;
+ }
+
+ Image image;
+ try{
+ image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId);
+ imageHandle = image.getImageHandle();
+ } catch (TskCoreException ex){
+ logger.log(Level.SEVERE, "Error loading image", ex);
+
+ // Stay subscribed to the events for now. Case close will clean everything up.
+ return;
+ }
- new Thread(() -> {
- try{
- Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(imageId);
- ProgressHandle progressHandle = null;
- ScheduledFuture> progressUpdateTask = null;
-
- if(doUI){
- progressHandle = ProgressHandle.createHandle("Image writer - " + name);
- progressHandle.start(100);
- progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate(
- new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS);
- }
+ logger.log(Level.INFO, String.format("Finishing VHD image for %s",
+ dataSourceName)); //NON-NLS
- synchronized(currentTasksLock){
- ImageWriter.this.imagesBeingFinished.add(image.getImageHandle());
-
- if(doUI){
- if(isCancelled){
- progressUpdateTask.cancel(true);
- return;
- }
- ImageWriter.this.progressUpdaters.add(progressUpdateTask);
- ImageWriter.this.progressBars.add(progressHandle);
- }
- }
+ if(doUI){
+ periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS
+ progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName);
+ progressHandle.start(100);
+ progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate(
+ new ProgressUpdateTask(progressHandle, imageHandle), 0, 250, TimeUnit.MILLISECONDS);
+ }
- // The added complexity here with the Future is because we absolutely need to make sure
- // the call to finishImageWriter returns before allowing the TSK data structures to be freed
- // during case close.
- Future> finishTask = Executors.newSingleThreadExecutor().submit(() -> {
- try{
- SleuthkitJNI.finishImageWriter(image.getImageHandle());
- } catch (TskCoreException ex){
- logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
- }
- });
- synchronized(currentTasksLock){
- ImageWriter.this.finishTasksInProgress.add(finishTask);
- }
-
- // Wait for finishImageWriter to complete
- try{
- // The call to get() will happen twice if the user closes the case, which is ok
- finishTask.get();
- } catch (InterruptedException | ExecutionException ex){
- logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
- }
-
- synchronized(currentTasksLock){
- ImageWriter.this.finishTasksInProgress.remove(finishTask);
- ImageWriter.this.imagesBeingFinished.remove(image.getImageHandle());
-
- if(doUI){
- progressUpdateTask.cancel(true);
- ImageWriter.this.progressUpdaters.remove(progressUpdateTask);
- progressHandle.finish();
- ImageWriter.this.progressBars.remove(progressHandle);
- }
- }
-
- logger.log(Level.INFO, String.format("Finished writing VHD image for %s", event.getDataSource().getName())); //NON-NLS
- } catch (TskCoreException ex){
- logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
- }
- }).start();
- } else {
- logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS
- }
- }
+ // The added complexity here with the Future is because we absolutely need to make sure
+ // the call to finishImageWriter returns before allowing the TSK data structures to be freed
+ // during case close.
+ numFinishJobsInProgress.incrementAndGet();
+ finishTask = Executors.newSingleThreadExecutor().submit(() -> {
+ try{
+ SleuthkitJNI.finishImageWriter(imageHandle);
+ } catch (TskCoreException ex){
+ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
}
});
}
- listenerStarted = true;
- }
-
- /**
- * Add a data source ID to the list of images to run finishImage on.
- * Also starts the listener if needed.
- * @param id The dataSource/Image ID
- */
- public void addDataSourceId(Long id){
- startListener();
- synchronized(dataSourceIdsLock){
- dataSourceIds.add(id);
+
+ // Wait for finishImageWriter to complete
+ try{
+ // The call to get() will happen twice if the user closes the case, which is ok
+ finishTask.get();
+ } catch (InterruptedException | ExecutionException ex){
+ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
}
+ numFinishJobsInProgress.decrementAndGet();
+
+ synchronized(currentTasksLock){
+ unsubscribeFromEvents();
+ if(doUI){
+ // Some of these may be called twice if the user closes the case
+ progressUpdateTask.cancel(true);
+ progressHandle.finish();
+ periodicTasksExecutor.shutdown();
+ }
+ isFinished = true;
+ }
+
+ logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS
}
/**
- * Stop any open progress update task, finish the progress bars, and tell
- * the finishImage process to stop
+ * Tell the finishImage process to stop and wait for it to do so.
*/
- public void close(){
+ private void close(){
synchronized(currentTasksLock){
isCancelled = true;
- for(ScheduledFuture> task:ImageWriter.this.progressUpdaters){
- task.cancel(true);
+ unsubscribeFromEvents();
+
+ // The case either got closed during ingest (before the startFinishImage got called)
+ // or an error occurred. If imageHandle is not null, we know that the first block of
+ // startFinishImage() completed so all the tasks have also been initialized.
+ if(imageHandle == null){
+ return;
}
- for(Long handle:imagesBeingFinished){
- SleuthkitJNI.cancelFinishImage(handle);
+ if(!isFinished){
+ SleuthkitJNI.cancelFinishImage(imageHandle);
logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS
- }
- // Wait for all the finish tasks to end
- for(Future> task:ImageWriter.this.finishTasksInProgress){
+ // Wait for the finish task to end
try{
- task.get();
+ finishTask.get();
} catch (InterruptedException | ExecutionException ex){
logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
}
- }
- for(ProgressHandle progressHandle:ImageWriter.this.progressBars){
+ // Stop the progress bar update task.
+ // The thread from startFinishImage will also stop it
+ // once the task completes, but we have to make absolutely sure
+ // this gets done before the Sleuthkit data structures are freed.
+ // Since we've stopped the update task, we'll stop the associated progress
+ // bar now, too.
+ progressUpdateTask.cancel(true);
progressHandle.finish();
}
}
}
+ /**
+ * Get the number of images currently being finished.
+ * @return number of images in progress
+ */
+ public static int numberOfJobsInProgress(){
+ return numFinishJobsInProgress.get();
+ }
+
/**
* Task to query the Sleuthkit processing to get the percentage done.
*/
private final class ProgressUpdateTask implements Runnable {
- long imageHandle;
- ProgressHandle progressHandle;
+ final long imageHandle;
+ final ProgressHandle progressHandle;
ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle){
this.imageHandle = imageHandle;
diff --git a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java
index e1af3697a6..4c54f587e8 100755
--- a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java
+++ b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanelAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,23 +27,19 @@ import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.casemodule.Case;
-@ActionID(
- category = "Help",
- id = "org.sleuthkit.autopsy.diagnostics.PerformancePanelAction"
-)
-@ActionRegistration(
- displayName = "#CTL_PerformancePanelAction",
- lazy=true
-)
+@ActionID(category = "Help", id = "org.sleuthkit.autopsy.diagnostics.PerformancePanelAction")
+@ActionRegistration(displayName = "#CTL_PerformancePanelAction", lazy = true)
@ActionReference(path = "Menu/Help", position = 1437)
public final class PerformancePanelAction extends CallableSystemAction {
+ private static final long serialVersionUID = 1L;
+
@Override
public void performAction() {
JDialog dialog = new PerformancePanel();
dialog.setVisible(true);
}
-
+
@Override
public boolean isEnabled() {
return Case.isCaseOpen();
@@ -56,8 +52,9 @@ public final class PerformancePanelAction extends CallableSystemAction {
@Override
public boolean asynchronous() {
- return false; // run on edt
+ return false;
}
+
@Override
public String getName() {
return NbBundle.getMessage(PerformancePanelAction.class, "CTL_PerformancePanelAction");
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterChildren.java
deleted file mode 100644
index 3acab048f5..0000000000
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterChildren.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2011 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.directorytree;
-
-import org.openide.explorer.ExplorerManager;
-import org.openide.nodes.FilterNode;
-import org.openide.nodes.Node;
-
-/**
- * This class is used for the creation of all the children for the
- * DataResultFilterNode that created in the DataResultFilterNode.java.
- *
- */
-class DataResultFilterChildren extends FilterNode.Children {
-
- ExplorerManager sourceEm;
-
- /**
- * the constructor
- */
- public DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
- super(arg);
- this.sourceEm = sourceEm;
- }
-
- @Override
- protected Node copyNode(Node arg0) {
- return new DataResultFilterNode(arg0, sourceEm);
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index de20df20e7..81820218ec 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,6 +23,8 @@ import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
+import java.util.prefs.PreferenceChangeEvent;
+import java.util.prefs.PreferenceChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.explorer.ExplorerManager;
@@ -33,6 +35,7 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
+import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType;
@@ -58,6 +61,7 @@ import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.LocalFile;
import org.sleuthkit.datamodel.SlackFile;
+import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.VirtualDirectory;
@@ -67,11 +71,37 @@ import org.sleuthkit.datamodel.VirtualDirectory;
*/
public class DataResultFilterNode extends FilterNode {
- private ExplorerManager sourceEm;
+ private static boolean filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
+ private static boolean filterKnownFromViews = UserPreferences.hideKnownFilesInViewsTree();
+ private static boolean filterSlackFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
+ private static boolean filterSlackFromViews = UserPreferences.hideSlackFilesInViewsTree();
- private final DisplayableItemNodeVisitor> getActionsDIV;
+ static {
+ UserPreferences.addChangeListener(new PreferenceChangeListener() {
+ @Override
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ switch (evt.getKey()) {
+ case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE:
+ filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
+ break;
+ case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE:
+ filterKnownFromViews = UserPreferences.hideKnownFilesInViewsTree();
+ break;
+ case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE:
+ filterSlackFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
+ break;
+ case UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE:
+ filterSlackFromViews = UserPreferences.hideSlackFilesInViewsTree();
+ break;
+ }
+ }
+ });
+ }
- private final DisplayableItemNodeVisitor getPreferredActionsDIV;
+ static private final DisplayableItemNodeVisitor> getActionsDIV = new GetPopupActionsDisplayableItemNodeVisitor();
+ private final DisplayableItemNodeVisitor getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor();
+
+ private final ExplorerManager sourceEm;
/**
*
@@ -81,8 +111,17 @@ public class DataResultFilterNode extends FilterNode {
public DataResultFilterNode(Node node, ExplorerManager em) {
super(node, new DataResultFilterChildren(node, em));
this.sourceEm = em;
- getActionsDIV = new GetPopupActionsDisplayableItemNodeVisitor();
- getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor();
+
+ }
+
+ /**
+ *
+ * @param node Root node to be passed to DataResult viewers
+ * @param em ExplorerManager for component that is creating the node
+ */
+ private DataResultFilterNode(Node node, ExplorerManager em, boolean filterKnown, boolean filterSlack) {
+ super(node, new DataResultFilterChildren(node, em, filterKnown, filterSlack));
+ this.sourceEm = em;
}
/**
@@ -150,6 +189,64 @@ public class DataResultFilterNode extends FilterNode {
return propertySets;
}
+ /**
+ * This class is used for the creation of all the children for the
+ * DataResultFilterNode that created in the DataResultFilterNode.java.
+ *
+ */
+ private static class DataResultFilterChildren extends FilterNode.Children {
+
+ private final ExplorerManager sourceEm;
+
+ private boolean filterKnown;
+ private boolean filterSlack;
+
+ /**
+ * the constructor
+ */
+ private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
+ super(arg);
+ switch (SelectionContext.getSelectionContext(arg)) {
+ case DATA_SOURCES:
+ filterSlack = filterSlackFromDataSources;
+ filterKnown = filterKnownFromDataSources;
+ break;
+ case VIEWS:
+ filterSlack = filterSlackFromViews;
+ filterKnown = filterKnownFromViews;
+ break;
+ default:
+ filterSlack = false;
+ filterKnown = false;
+ break;
+ }
+ this.sourceEm = sourceEm;
+ }
+
+ private DataResultFilterChildren(Node arg, ExplorerManager sourceEm, boolean filterKnown, boolean filterSlack) {
+ super(arg);
+ this.filterKnown = filterKnown;
+ this.filterSlack = filterSlack;
+ this.sourceEm = sourceEm;
+ }
+
+ @Override
+ protected Node[] createNodes(Node key) {
+ AbstractFile file = key.getLookup().lookup(AbstractFile.class);
+ if (file != null) {
+ if (filterKnown && (file.getKnown() == TskData.FileKnown.KNOWN)) {
+ // Filter out child nodes that represent known files
+ return new Node[]{};
+ }
+ if (filterSlack && file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
+ // Filter out child nodes that represent slack files
+ return new Node[]{};
+ }
+ }
+ return new Node[]{new DataResultFilterNode(key, sourceEm, filterKnown, filterSlack)};
+ }
+ }
+
/**
* Uses the default nodes actions per node, adds some custom ones and
* returns them per visited node type
@@ -236,10 +333,10 @@ public class DataResultFilterNode extends FilterNode {
// The base class Action is "Collapse All", inappropriate.
return null;
}
-
+
@Override
public List visit(FileTypesNode fileTypes) {
- return defaultVisit(fileTypes);
+ return defaultVisit(fileTypes);
}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java
index 0ae016bd00..8c2595969a 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,12 +18,10 @@
*/
package org.sleuthkit.autopsy.directorytree;
-import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
-import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
@@ -32,8 +30,7 @@ import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractContentNode;
-import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
-import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Directory;
@@ -42,86 +39,83 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
/**
- * This class sets the actions for the nodes in the directory tree and creates
- * the children filter so that files and such are hidden from the tree.
- *
+ * A node filter (decorator) that sets the actions for the nodes in the
+ * directory tree and wraps the node children with a
+ * DirectoryTreeFilterChildren.
*/
class DirectoryTreeFilterNode extends FilterNode {
- private static final Action collapseAll = new CollapseAction(
- NbBundle.getMessage(DirectoryTreeFilterNode.class, "DirectoryTreeFilterNode.action.collapseAll.text"));
-
private static final Logger logger = Logger.getLogger(DirectoryTreeFilterNode.class.getName());
+ private static final Action collapseAllAction = new CollapseAction(NbBundle.getMessage(DirectoryTreeFilterNode.class, "DirectoryTreeFilterNode.action.collapseAll.text"));
/**
- * the constructor
+ * A node filter (decorator) that sets the actions for the nodes in the
+ * directory tree and wraps the node children with a
+ * DirectoryTreeFilterChildren.
+ *
+ * @param nodeToWrap The node to wrap.
+ * @param createChildren Whether to create the children of the wrapped node
+ * or treat it a a leaf node.
*/
- DirectoryTreeFilterNode(Node arg, boolean createChildren) {
- super(arg, DirectoryTreeFilterChildren.createInstance(arg, createChildren),
- new ProxyLookup(Lookups.singleton(new OriginalNode(arg)),
- arg.getLookup()));
+ DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) {
+ super(nodeToWrap,
+ DirectoryTreeFilterChildren.createInstance(nodeToWrap, createChildren),
+ new ProxyLookup(Lookups.singleton(new OriginalNode(nodeToWrap)), nodeToWrap.getLookup()));
}
+ /**
+ * Gets the display name for the node, possibly including a child count in
+ * parentheses.
+ *
+ * @return The display name for the node.
+ */
@Override
public String getDisplayName() {
final Node orig = getOriginal();
-
String name = orig.getDisplayName();
-
- //do not show children counts for non content nodes
if (orig instanceof AbstractContentNode) {
- //show only for file content nodes
AbstractFile file = getLookup().lookup(AbstractFile.class);
if (file != null) {
try {
final int numChildren = file.getChildrenCount();
-
- // left-to-right marks here are necessary to keep the count and parens together
- // for mixed right-to-left and left-to-right names
- name = name + " \u200E(\u200E" + numChildren + ")\u200E";
+ /*
+ * Left-to-right marks here are necessary to keep the count
+ * and parens together for mixed right-to-left and
+ * left-to-right names.
+ */
+ name = name + " \u200E(\u200E" + numChildren + ")\u200E"; //NON-NLS
+
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting children count to display for file: " + file, ex); //NON-NLS
}
-
}
}
-
return name;
}
/**
- * Right click action for the nodes in the directory tree.
+ * Gets the context mneu (right click menu) actions for the node.
*
- * @param popup
+ * @param context Whether to find actions for context meaning or for the
+ * node itself.
*
* @return
*/
@Override
- public Action[] getActions(boolean popup) {
+ public Action[] getActions(boolean context) {
List actions = new ArrayList<>();
-
final Content content = this.getLookup().lookup(Content.class);
if (content != null) {
- actions.addAll(DirectoryTreeFilterNode.getDetailActions(content));
+ actions.addAll(ExplorerNodeActionVisitor.getActions(content));
- //extract dir action
Directory dir = this.getLookup().lookup(Directory.class);
if (dir != null) {
actions.add(ExtractAction.getInstance());
- actions.add(new AbstractAction(
- NbBundle.getMessage(this.getClass(), "DirectoryTreeFilterNode.action.runIngestMods.text")) {
- @Override
- public void actionPerformed(ActionEvent e) {
- final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(dir);
- ingestDialog.display();
- }
- });
+ actions.add(new RunIngestModulesAction(dir));
}
final Image img = this.getLookup().lookup(Image.class);
-
- VirtualDirectory virtualDirectory = this.getLookup().lookup(VirtualDirectory.class);
- // determine if the virtualDireory is at root-level (Logical File Set).
+ final VirtualDirectory virtualDirectory = this.getLookup().lookup(VirtualDirectory.class);
boolean isRootVD = false;
if (virtualDirectory != null) {
try {
@@ -132,48 +126,15 @@ class DirectoryTreeFilterNode extends FilterNode {
logger.log(Level.WARNING, "Error determining the parent of the virtual directory", ex); // NON-NLS
}
}
-
- // 'run ingest' action and 'file search' action are added only if the
- // selected node is img node or a root level virtual directory.
if (img != null || isRootVD) {
- actions.add(new FileSearchAction(
- NbBundle.getMessage(this.getClass(), "DirectoryTreeFilterNode.action.openFileSrcByAttr.text")));
- actions.add(new AbstractAction(
- NbBundle.getMessage(this.getClass(), "DirectoryTreeFilterNode.action.runIngestMods.text")) {
- @Override
- public void actionPerformed(ActionEvent e) {
- final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.singletonList(content));
- ingestDialog.display();
- }
- });
+ actions.add(new FileSearchAction(NbBundle.getMessage(this.getClass(), "DirectoryTreeFilterNode.action.openFileSrcByAttr.text")));
+ actions.add(new RunIngestModulesAction(Collections.singletonList(content)));
}
}
-
- //check if delete actions should be added
- final Node orig = getOriginal();
- //TODO add a mechanism to determine if DisplayableItemNode
- if (orig instanceof DisplayableItemNode) {
- actions.addAll(getDeleteActions((DisplayableItemNode) orig));
- }
-
- actions.add(collapseAll);
+ actions.add(collapseAllAction);
return actions.toArray(new Action[actions.size()]);
}
- private static List getDeleteActions(DisplayableItemNode original) {
- List actions = new ArrayList<>();
- //actions.addAll(original.accept(getDeleteActionVisitor));
- return actions;
- }
-
- private static List getDetailActions(Content c) {
- List actions = new ArrayList<>();
-
- actions.addAll(ExplorerNodeActionVisitor.getActions(c));
-
- return actions;
- }
-
//FIXME: this seems like a big hack -jm
public static class OriginalNode {
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form
index 9504548823..d77daea41e 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form
@@ -37,9 +37,9 @@
-
-
-
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
index 20ce2db7e8..da788f4363 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.directorytree;
-import org.sleuthkit.autopsy.datamodel.EmptyNode;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
@@ -62,15 +61,14 @@ import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.DataSources;
import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
+import org.sleuthkit.autopsy.datamodel.EmptyNode;
import org.sleuthkit.autopsy.datamodel.ExtractedContent;
import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType;
import org.sleuthkit.autopsy.datamodel.KeywordHits;
-import org.sleuthkit.autopsy.datamodel.KnownFileFilterNode;
import org.sleuthkit.autopsy.datamodel.Reports;
import org.sleuthkit.autopsy.datamodel.Results;
import org.sleuthkit.autopsy.datamodel.ResultsNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
-import org.sleuthkit.autopsy.datamodel.SlackFileFilterNode;
import org.sleuthkit.autopsy.datamodel.Tags;
import org.sleuthkit.autopsy.datamodel.Views;
import org.sleuthkit.autopsy.datamodel.ViewsNode;
@@ -231,8 +229,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
.addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(showRejectedCheckBox))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 838, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap())
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 854, Short.MAX_VALUE)
+ .addGap(0, 0, 0))
);
}// //GEN-END:initComponents
@@ -297,6 +295,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* only, i.e. deserialization routines; otherwise you could get a
* non-deserialized instance. To obtain the singleton instance, use
* {@link #findInstance}.
+ *
+ * @return instance - the default instance
*/
public static synchronized DirectoryTreeTopComponent getDefault() {
if (instance == null) {
@@ -308,6 +308,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
/**
* Obtain the DirectoryTreeTopComponent instance. Never call
* {@link #getDefault} directly!
+ *
+ * @return getDefault() - the default instance
*/
public static synchronized DirectoryTreeTopComponent findInstance() {
WindowManager winManager = WindowManager.getDefault();
@@ -349,97 +351,101 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- if (Case.isCaseOpen()) {
- Case currentCase = Case.getCurrentCase();
+ Case currentCase = null;
+ try {
+ currentCase = Case.getCurrentCase();
+ } catch (IllegalStateException ex) {
+ /*
+ * No open case.
+ */
+ }
- // close the top component if there's no image in this case
- if (currentCase.hasData() == false) {
- //this.close();
- ((BeanTreeView) this.jScrollPane1).setRootVisible(false); // hide the root
- } else {
- // if there's at least one image, load the image and open the top component
- List