diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index fb43fea301..2d905e7e49 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -258,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/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
index 12184925e9..c4a51ac904 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
@@ -15,12 +15,6 @@ NewCaseVisualPanel1.caseNameTextField.text_1=
NewCaseVisualPanel1.jLabel2.text_1=Case data will be stored in the following directory:
NewCaseVisualPanel1.caseParentDirTextField.text=
NewCaseVisualPanel1.caseDirTextField.text_1=
-CasePropertiesForm.caseDirLabel.text=Case Directory:
-CasePropertiesForm.crDateLabel.text=Created Date:
-CasePropertiesForm.caseNameLabel.text=Case Name:
-CasePropertiesForm.caseNameTextField.text=
-CasePropertiesForm.updateCaseNameButton.text=Update Name
-CasePropertiesForm.deleteCaseButton.text=Delete Case
CueBannerPanel.autopsyLogo.text=
CueBannerPanel.createNewLabel.text=Create New Case
CueBannerPanel.openLabel.text=Open Existing Case
@@ -31,8 +25,6 @@ CueBannerPanel.openCaseButton.text=
CueBannerPanel.openRecentButton.text=
OpenRecentCasePanel.cancelButton.text=Cancel
OpenRecentCasePanel.jLabel1.text=Recent Cases
-CasePropertiesForm.caseNumberLabel.text=Case Number:
-CasePropertiesForm.examinerLabel.text=Examiner:
NewCaseVisualPanel2.caseNumberTextField.text=
NewCaseVisualPanel2.examinerLabel.text=Examiner:
NewCaseVisualPanel2.caseNumberLabel.text=Case Number:
@@ -98,12 +90,9 @@ AddImageWizardIngestConfigVisual.getName.text=Configure Ingest Modules
AddImageWizardIterator.stepXofN=Step {0} of {1}
AddLocalFilesTask.localFileAdd.progress.text=Adding\: {0}/{1}
Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\!
-Case.create.exception.msg=Error creating a case\: {0} in dir {1}
Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-user.
-Case.open.exception.blankCase.msg=Case name is blank.
Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the database with the following path has been made\:\n {0}
Case.open.msgDlg.updated.title=Case Database Schema Update
-Case.open.exception.checkFile.msg=Case file must have {0} extension.
Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-user.
Case.checkImgExist.confDlg.doesntExist.msg={0} has detected that one of the images associated with \n\
this case are missing. Would you like to search for them now?\n\
@@ -113,9 +102,6 @@ Please note that you will still be able to browse directories and generate repor
if you choose No, but you will not be able to view file content or run the ingest process.
Case.checkImgExist.confDlg.doesntExist.title=Missing Image
Case.addImg.exception.msg=Error adding image to the case
-Case.closeCase.exception.msg=Error while trying to close the current case.
-Case.deleteCase.exception.msg=Error deleting the case dir\: {0}
-Case.deleteCase.exception.msg2=Error deleting the case dir\: {0}
Case.updateCaseName.exception.msg=Error while trying to update the case name.
Case.updateExaminer.exception.msg=Error while trying to update the examiner.
Case.updateCaseNum.exception.msg=Error while trying to update the case number.
@@ -133,7 +119,6 @@ Case.GetCaseTypeGivenPath.Failure=Unable to get case type
Case.metaDataFileCorrupt.exception.msg=The case metadata file (.aut) is corrupted.
Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk.
Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1}
-Case.exception.errorLocking=Unable to open case being updated by another user.
CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\
Case Name\: {0}\n\
Case Directory\: {1}
@@ -225,8 +210,6 @@ NewCaseVisualPanel1.caseParentDirWarningLabel.text=
NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user
NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user
NewCaseVisualPanel1.caseTypeLabel.text=Case Type:
-CasePropertiesForm.lbDbType.text=Case Type:
-CasePropertiesForm.lbDbName.text=Database Name:
SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist!
SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user!
SingleUserCaseConverter.NonUniqueDatabaseName=Database name not unique.
@@ -241,3 +224,13 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default
IngestJobInfoPanel.jLabel1.text=Ingest Modules
IngestJobInfoPanel.jLabel2.text=Ingest Jobs
CaseInformationPanel.closeButton.text=Close
+CasePropertiesPanel.updateCaseNameButton.text=Update Name
+CasePropertiesPanel.caseNameTextField.text=
+CasePropertiesPanel.caseDirLabel.text=Case Directory:
+CasePropertiesPanel.crDateLabel.text=Created Date:
+CasePropertiesPanel.caseNameLabel.text=Case Name:
+CasePropertiesPanel.lbDbName.text=Database Name:
+CasePropertiesPanel.lbDbType.text=Case Type:
+CasePropertiesPanel.examinerLabel.text=Examiner:
+CasePropertiesPanel.caseNumberLabel.text=Case Number:
+CasePropertiesPanel.deleteCaseButton.text=Delete Case
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties
index b033efa9ca..fdb39ad133 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties
@@ -1,4 +1,3 @@
-CTL_AddImage=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0...
CTL_AddImageButton=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0
CTL_CaseCloseAct=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b
CTL_CaseNewAction=\u65b0\u898f\u30b1\u30fc\u30b9...
@@ -12,19 +11,12 @@ NewCaseVisualPanel1.caseNameLabel.text_1=\u30b1\u30fc\u30b9\u540d\uff1a
NewCaseVisualPanel1.caseDirLabel.text=\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a
NewCaseVisualPanel1.caseDirBrowseButton.text=\u95b2\u89a7
NewCaseVisualPanel1.jLabel2.text_1=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u306f\u6b21\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3059\uff1a
-CasePropertiesForm.caseDirLabel.text=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a
-CasePropertiesForm.crDateLabel.text=\u4f5c\u6210\u65e5\uff1a
-CasePropertiesForm.caseNameLabel.text=\u30b1\u30fc\u30b9\u540d\uff1a
-CasePropertiesForm.updateCaseNameButton.text=\u66f4\u65b0
-CasePropertiesForm.deleteCaseButton.text=\u30b1\u30fc\u30b9\u3092\u524a\u9664
CueBannerPanel.createNewLabel.text=\u65b0\u898f\u30b1\u30fc\u30b9\u3092\u4f5c\u6210
CueBannerPanel.openLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f
CueBannerPanel.closeButton.text=\u9589\u3058\u308b
CueBannerPanel.openRecentLabel.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f
OpenRecentCasePanel.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb
OpenRecentCasePanel.jLabel1.text=\u6700\u8fd1\u958b\u3044\u305f\u30d5\u30a1\u30a4\u30eb
-CasePropertiesForm.caseNumberLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a
-CasePropertiesForm.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uff1a
NewCaseVisualPanel2.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uff1a
NewCaseVisualPanel2.caseNumberLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a
NewCaseVisualPanel2.optionalLabel.text=\u30aa\u30d7\u30b7\u30e7\u30ca\u30eb\uff1a\u30b1\u30fc\u30b9\u756a\u53f7\u304a\u3088\u3073\u8abf\u67fb\u62c5\u5f53\u8005\u3092\u8a2d\u5b9a
@@ -80,21 +72,15 @@ AddImageWizardIngestConfigVisual.getName.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30
AddImageWizardIterator.stepXofN=\u30b9\u30c6\u30c3\u30d7{0}\uff0f{1}
AddLocalFilesTask.localFileAdd.progress.text=\u8ffd\u52a0\u4e2d\uff1a{0}/{1}
Case.getCurCase.exception.noneOpen=\u4f5c\u696d\u4e2d\u306e\u30b1\u30fc\u30b9\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\uff1b\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\uff01
-Case.create.exception.msg=\u30b1\u30fc\u30b9\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{1}\u306e{0}
-Case.open.exception.blankCase.msg=\u30b1\u30fc\u30b9\u540d\u304c\u7a7a\u767d\u3067\u3059\u3002
Case.open.msgDlg.updated.msg=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002\n\u6b21\u306e\u30d1\u30b9\u3092\u6301\u3064\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u30b3\u30d4\u30fc\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\uff1a\n\
{0}
Case.open.msgDlg.updated.title=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0
-Case.open.exception.checkFile.msg=\u30b1\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u306f{0}\u62e1\u5f35\u5b50\u304c\u5fc5\u8981\u3067\u3059\u3002
Case.checkImgExist.confDlg.doesntExist.msg={0} \u304c\u3053\u306e\u30b1\u30fc\u30b9\u306b\u95a2\u9023\u3059\u308b\u30a4\u30e1\u30fc\u30b8\u306e\u3046\u3061\uff11\u3064\u304c\u6b20\u843d\u3057\u3066\u3044\u308b\u306e\u3092\u691c\u51fa\u3057\u307e\u3057\u305f\u3002\u305d\u308c\u3092\u4eca\u304b\u3089\u691c\u7d22\u3057\u307e\u3059\u304b\uff1f\n\n\
\u4ee5\u524d\u3001\u30a4\u30e1\u30fc\u30b8\u306f\u6b21\u306b\u3042\u308a\u307e\u3057\u305f\uff1a\n\
{1}\n\
\u3044\u3044\u3048\u3092\u9078\u629e\u3057\u3066\u3082\u3001\u4eca\u5f8c\u3082\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u95b2\u89a7\u3057\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u304c\u3067\u304d\u307e\u3059\u304c\u3001\n\u30d5\u30a1\u30a4\u30eb\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u8868\u793a\u307e\u305f\u306f\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30d7\u30ed\u30bb\u30b9\u306e\u5b9f\u884c\u304c\u3067\u304d\u306a\u304f\u306a\u308a\u307e\u3059\u3002
Case.checkImgExist.confDlg.doesntExist.title=\u6b20\u843d\u3057\u3066\u3044\u308b\u30a4\u30e1\u30fc\u30b8
Case.addImg.exception.msg=\u30b1\u30fc\u30b9\u306b\u30a4\u30e1\u30fc\u30b8\u3092\u8ffd\u52a0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
-Case.closeCase.exception.msg=\u4f5c\u696d\u4e2d\u306e\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b\u6700\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
-Case.deleteCase.exception.msg=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a{0}
-Case.deleteCase.exception.msg2=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a{0}
Case.updateCaseName.exception.msg=\u30b1\u30fc\u30b9\u540d\u3092\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
Case.updateExaminer.exception.msg=\u8abf\u67fb\u62c5\u5f53\u8005\u3092\u66f4\u65b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
Case.updateCaseNum.exception.msg=\u30b1\u30fc\u30b9\u756a\u53f7\u3092\u66f4\u65b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
@@ -203,8 +189,6 @@ CollaborationMonitor.addingDataSourceStatus.msg={0}\u304c\u30c7\u30fc\u30bf\u30d
CollaborationMonitor.analyzingDataSourceStatus.msg={0}\u304c{1}\u3092\u89e3\u6790\u4e2d
NewCaseVisualPanel1.multiUserCaseRadioButton.text=\u8907\u6570\u30e6\u30fc\u30b6\u30fc
NewCaseVisualPanel1.singleUserCaseRadioButton.text=\u5358\u6570\u30e6\u30fc\u30b6\u30fc
-CasePropertiesForm.lbDbType.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\uff1a
-CasePropertiesForm.lbDbName.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\uff1a
SingleUserCaseConverter.BadDatabaseFileName=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u307e\u305b\u3093\uff01
SingleUserCaseConverter.AlreadyMultiUser=\u30b1\u30fc\u30b9\u306f\u65e2\u306b\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u3067\u3059\uff01
SingleUserCaseConverter.NonUniqueDatabaseName=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\u304c\u30e6\u30cb\u30fc\u30af\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
@@ -216,3 +200,12 @@ Case_caseType_multiUser=\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9
Case_caseType_singleUser=\u5358\u6570\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9
CasePropertiesForm.imagesTable.columnModel.title0=\u30d1\u30b9
CasePropertiesForm.imagesTable.columnModel.title1=\u524a\u9664
+CasePropertiesPanel.updateCaseNameButton.text=\u66f4\u65b0
+CasePropertiesPanel.caseDirLabel.text=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\uff1a
+CasePropertiesPanel.crDateLabel.text=\u4f5c\u6210\u65e5\uff1a
+CasePropertiesPanel.caseNameLabel.text=\u30b1\u30fc\u30b9\u540d\uff1a
+CasePropertiesPanel.lbDbName.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\uff1a
+CasePropertiesPanel.lbDbType.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\uff1a
+CasePropertiesPanel.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uff1a
+CasePropertiesPanel.caseNumberLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a
+CasePropertiesPanel.deleteCaseButton.text=\u30b1\u30fc\u30b9\u3092\u524a\u9664
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index d681e81b48..ad21349a82 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.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,14 +18,20 @@
*/
package org.sleuthkit.autopsy.casemodule;
-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;
@@ -33,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;
@@ -62,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;
@@ -78,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
@@ -107,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;
}
/**
@@ -128,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();
@@ -139,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.
*/
@@ -174,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 {
@@ -187,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,
/**
@@ -202,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,
/**
@@ -210,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,
/**
@@ -218,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,
/**
@@ -228,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,
/**
@@ -242,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.
*/
@@ -255,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.
*/
@@ -275,38 +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 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.
@@ -372,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.
*/
@@ -381,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;
}
/**
@@ -410,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;
}
/**
@@ -441,7 +1188,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
/**
- * Gets the case name.
+ * Gets the immutable case name.
*
* @return The case name.
*/
@@ -450,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();
}
/**
@@ -601,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.
*
@@ -625,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;
}
@@ -651,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.
*
@@ -783,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));
}
@@ -796,7 +1522,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* database.
*/
public List getAllReports() throws TskCoreException {
- return this.db.getAllReports();
+ return this.caseDb.getAllReports();
}
/**
@@ -809,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=Acquiring locks..."
+ })
+ 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.
*
@@ -840,834 +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);
- 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;
- }
-
- /**
- * 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();
}
/**
@@ -1725,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.
*
@@ -1749,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();
}
/**
@@ -1789,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();
}
/**
@@ -1813,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();
}
/**
@@ -1856,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..e7b4e98c99 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,11 +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);
+ }
+
+ /*
+ * 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.
+ */
+ Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
+ if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
+ Path caseDirectoryPath = Paths.get(getCaseDirectory());
+ this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
}
/*
@@ -483,4 +508,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..a5064606f2 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,60 @@ 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 | 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.getMessage(),
+ 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 1cceb7ab12..f76f620413 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 015f7751ea..305bb3dd90 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/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/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 51e0ac87d3..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
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties
index abb22c4ce8..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
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/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/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 2b7f7078a9..7c28e09529 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.datamodel;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
@@ -130,9 +131,11 @@ public class ArtifactStringContent implements StringContent {
case INTEGER:
case LONG:
case DOUBLE:
- case BYTE:
buffer.append(attr.getDisplayString());
break;
+ case BYTE:
+ buffer.append(Arrays.toString(attr.getValueBytes()));
+ break;
case DATETIME:
long epoch = attr.getValueLong();
String time = "0000-00-00 00:00:00";
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/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/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/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
index 4cbf5fa283..51b4bf7cb1 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
@@ -353,97 +353,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