diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 28a8815bca..6e9df6d3f1 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -242,6 +242,7 @@ org.sleuthkit.autopsy.events org.sleuthkit.autopsy.externalresults org.sleuthkit.autopsy.filesearch + org.sleuthkit.autopsy.framework org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions 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 cf5a02a0e4..26429c1c5a 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,26 +39,26 @@ 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.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.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.openide.util.Exceptions; 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; @@ -64,11 +70,12 @@ 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; import org.sleuthkit.autopsy.core.UserPreferencesException; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl; import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.FileUtil; @@ -81,27 +88,84 @@ 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.ingest.RunIngestAction; +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 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. + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item set + * by Installers. + */ + private static String appName; + + /* + * The following fields are the state associated with the currently open + * case. The coordination service lock on the case directory of the + * currently open is used to prevent deletion of a multi-user case by this + * node if it is open in another node. The case locking executor is a + * single-threaded executor to guarantee that the case directory lock is + * acquired and released in the same thread, as required by the coordination + * service. + */ + private static Case currentCase; + private static CoordinationService.Lock currentCaseDirLock; + private static ExecutorService caseLockingExecutor; + + /* + * Case instance data. + */ + private CaseMetadata caseMetadata; + private SleuthkitCase caseDb; + private SleuthkitErrorReporter sleuthkitErrorReporter; + private CollaborationMonitor collaborationMonitor; + private Services services; + 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 @@ -110,12 +174,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; } /** @@ -131,8 +204,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(); @@ -142,30 +219,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. */ @@ -177,8 +245,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 { @@ -190,8 +258,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, /** @@ -205,7 +273,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, /** @@ -213,7 +281,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, /** @@ -221,7 +289,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, /** @@ -245,7 +313,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. */ @@ -258,8 +326,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. */ @@ -278,38 +345,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. @@ -375,7 +410,300 @@ 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. + * + * @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.creationException.couldNotCreateCase=Could not create case: {0}", + "Case.exceptionMessage.illegalCaseName=Case name contains illegal characters.", + "Case.exceptionMessage.lockAcquisitionInterrupted=Acquiring locks was interrupted.", + "Case.progressIndicatorTitle.creatingCase=Creating Case", + "Case.progressIndicatorCancelButton.label=Cancel", + "Case.progressMessage.preparing=Preparing...", + "Case.progressMessage.acquiringLocks=Acquiring locks...", + "Case.progressMessage.finshing=Finishing..." + }) + public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + /* + * If running with the desktop GUI, this needs to be done before any + * cases are created or opened so that the application name can be + * captured before a case name is added to the title. + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item + * set by an Installer. + */ + if (RuntimeProperties.runningWithGUI()) { + getAppNameFromMainWindow(); + } + + /* + * If another case is open, close it. + */ + if (null != currentCase) { + closeCurrentCase(); + } + + /* + * 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_creationException_couldNotCreateCase(Bundle.Case_exceptionMessage_illegalCaseName()), ex); + } + logger.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS + + /* + * Set up either a GUI progress indicator or a logging progress + * indicator. + */ + CancelButtonListener listener = new CancelButtonListener(); + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_creatingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), null, listener); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + Case newCase = new Case(); + CaseContext caseContext = new CaseContext(newCase, progressIndicator); + listener.setCaseContext(caseContext); + 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. + */ + try { + Future future = getCaseLockingExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == caseType) { + newCase.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 { + newCase.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 newCase; + }); + if (RuntimeProperties.runningWithGUI()) { + listener.setCaseActionFuture(future); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + } + currentCase = future.get(); + logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseOpened(); + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); + + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof InterruptedException) { + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); + } else { + throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex); + } + } finally { + progressIndicator.finish(Bundle.Case_progressMessage_finshing()); + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + } + } + } + + /** + * Opens an existing 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. + */ + @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 { + /* + * If running with the desktop GUI, this needs to be done before any + * cases are created or opened so that the application name can be + * captured before a case name is added to the title. + * + * TODO (JIRA-2231): Make the application name a RuntimeProperties item + * set by an Installer. + */ + if (RuntimeProperties.runningWithGUI()) { + getAppNameFromMainWindow(); + } + + /* + * If another case is open, close it. + */ + if (null != currentCase) { + closeCurrentCase(); + } + + logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + try { + CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); + + /* + * Set up either a GUI progress indicator or a logging progress + * indicator. + */ + CancelButtonListener listener = new CancelButtonListener(); + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator(Bundle.Case_progressIndicatorTitle_openingCase(), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), null, listener); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + Case caseToOpen = new Case(); + CaseContext caseContext = new CaseContext(caseToOpen, progressIndicator); + listener.setCaseContext(caseContext); + progressIndicator.start(Bundle.Case_progressMessage_preparing()); + + /* + * Opening 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. + */ + CaseType caseType = metadata.getCaseType(); + String caseName = metadata.getCaseName(); + try { + Future future = getCaseLockingExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == caseType) { + caseToOpen.open(metadata, 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(metadata.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(metadata.getCaseName())) { + assert (null != resourcesLock); + try { + caseToOpen.open(metadata, 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 caseToOpen; + }); + if (RuntimeProperties.runningWithGUI()) { + listener.setCaseActionFuture(future); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + } + future.get(); + currentCase = future.get(); + logger.log(Level.INFO, "Opened case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseOpened(); + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); + } + } finally { + progressIndicator.finish(Bundle.Case_progressMessage_finshing()); + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + } + } + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex); + } + } + + /** + * Checks if a case, the current case, is open. * * @return True or false. */ @@ -398,13 +726,677 @@ public class Case implements SleuthkitCase.ErrorObserver { } } + /** + * Closes the current case if there is a current case. + * + * @throws CaseActionException if there is a problem closing 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.closeException.couldNotCloseCase=Error closing case: {0}", + "Case.progressIndicatorTitle.closingCase=Closing Case" + }) + public static void closeCurrentCase() throws CaseActionException { + if (null == currentCase) { + return; + } + + /* + * Set up either a GUI progress indicator or a logging progress + * indicator. + */ + CancelButtonListener listener = new CancelButtonListener(); + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator( + Bundle.Case_progressIndicatorTitle_closingCase(), + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + null, + listener); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + CaseContext caseContext = new CaseContext(currentCase, progressIndicator); + listener.setCaseContext(caseContext); + progressIndicator.start(Bundle.Case_progressMessage_preparing()); + + logger.log(Level.INFO, "Closing case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS + try { + /* + * 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 = getCaseLockingExecutor().submit(() -> { + if (CaseType.SINGLE_USER_CASE == currentCase.getCaseType()) { + currentCase.close(progressIndicator); + } else { + String caseName = currentCase.getCaseMetadata().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(currentCase.getName())) { + assert (null != resourcesLock); + currentCase.close(progressIndicator); + } finally { + /* + * Always release the case directory lock that was + * acquired when the case was opened. + */ + releaseSharedCaseDirLock(caseName); + } + } + return null; + }); + if (RuntimeProperties.runningWithGUI()) { + listener.setCaseActionFuture(future); + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true)); + } + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); + } + } finally { + /* + * The case is no longer the current case, even if an exception was + * thrown. + */ + logger.log(Level.INFO, "Closed case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), currentCase, null)); + currentCase = null; + if (RuntimeProperties.runningWithGUI()) { + updateGUIForCaseClosed(); + } + progressIndicator.finish(Bundle.Case_progressMessage_finshing()); + if (RuntimeProperties.runningWithGUI()) { + SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false)); + } + } + } + + /** + * Deletes the current case. + * + * @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 { + 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..." + }) + public static void deleteCase(CaseMetadata metadata) throws CaseActionException { + if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(metadata.getCaseDirectory())) { + throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase())); + } + + logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS + try { + /* + * Set up either a GUI progress indicator or a logging progress + * indicator. + */ + CancelButtonListener listener = new CancelButtonListener(); + ProgressIndicator progressIndicator; + if (RuntimeProperties.runningWithGUI()) { + progressIndicator = new ModalDialogProgressIndicator( + Bundle.Case_progressIndicatorTitle_deletingCase(), + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + null, + listener); + } else { + progressIndicator = new LoggingProgressIndicator(); + } + progressIndicator.start(Bundle.Case_progressMessage_preparing()); + Future future = getCaseLockingExecutor().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; + }); + logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS + future.get(); + } catch (InterruptedException | ExecutionException ex) { + if (ex instanceof ExecutionException) { + throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(ex.getCause().getMessage()), ex); + } else { + throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex); + } + } + } + + /** + * 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; + } + + /** + * Use the main window of the desktop application to initialize the + * application name. Should be called BEFORE any case is opened or created. + */ + private static void getAppNameFromMainWindow() { + /* + * 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. + */ + if (RuntimeProperties.runningWithGUI() && (null == appName || appName.isEmpty())) { + try { + SwingUtilities.invokeAndWait(() -> { + appName = WindowManager.getDefault().getMainWindow().getTitle(); + }); + } catch (InterruptedException | InvocationTargetException ex) { + logger.log(Level.SEVERE, "Unexpected exception getting main window title", 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 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 static void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { + try { + currentCaseDirLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir, SHARED_DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); + if (null == currentCaseDirLock) { + 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 static void releaseSharedCaseDirLock(String caseDir) { + if (currentCaseDirLock != null) { + try { + currentCaseDirLock.release(); + currentCaseDirLock = null; + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), 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() { + if (RuntimeProperties.runningWithGUI() && null != currentCase) { + + SleuthkitCase caseDb = currentCase.getSleuthkitCase(); + + /* + * If the case database was upgraded for a new schema and a backup + * database was created, notify the user. + */ + final String backupDbPath = caseDb.getBackupDatabasePath(); + if (null != backupDbPath) { + SwingUtilities.invokeLater(() -> { + 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 ret = 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 (ret == 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. + */ + 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); + 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(currentCase.getDisplayName(), currentCase.getCaseMetadata().getFilePath().toString()); + + /* + * Open the top components (windows within the main application + * window). + */ + if (currentCase.hasData()) { + CoreComponentControl.openCoreWindows(); + } + + /* + * Reset the main window title to be [curent case display name] + * - [application name], instead of just the application name. + */ + addCaseNameToMainWindowTitle(currentCase.getDisplayName()); + }); + } + } + + /* + * Update the GUI to to reflect the lack of a current case. + */ + private static void updateGUIForCaseClosed() { + if (RuntimeProperties.runningWithGUI()) { + 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); + } + } + + /** + * Get the single thread executor for the current case, creating it if + * necessary. + * + * @return The executor + */ + private static ExecutorService getCaseLockingExecutor() { + if (null == caseLockingExecutor) { + caseLockingExecutor = Executors.newSingleThreadExecutor(); + } + return caseLockingExecutor; + } + + /** + * 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; } /** @@ -416,15 +1408,6 @@ public class Case implements SleuthkitCase.ErrorObserver { return services; } - /** - * Gets the case metadata. - * - * @return A CaseMetaData object. - */ - CaseMetadata getCaseMetadata() { - return caseMetadata; - } - /** * Gets the case type. * @@ -444,7 +1427,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } /** - * Gets the case name. + * Gets the immutable case name. * * @return The case name. */ @@ -453,30 +1436,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(); } /** @@ -604,20 +1569,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. * @@ -628,7 +1579,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; } @@ -654,6 +1605,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. * @@ -662,18 +1624,6 @@ public class Case implements SleuthkitCase.ErrorObserver { public String getTextIndexName() { return getCaseMetadata().getTextIndexName(); } - - /** - * Sets the name of the keyword search index for the case. - * - * @param indexName The index name. - * - * @throws - * org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException - */ - public void setTextIndexName(String indexName) throws CaseMetadataException { - caseMetadata.setTextIndexName(indexName); - } /** * Queries whether or not the case has data, i.e., whether or not at least @@ -798,7 +1748,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)); } @@ -811,7 +1761,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * database. */ public List getAllReports() throws TskCoreException { - return this.db.getAllReports(); + return this.caseDb.getAllReports(); } /** @@ -824,425 +1774,484 @@ public class Case implements SleuthkitCase.ErrorObserver { */ public void deleteReports(Collection 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; } /** - * Closes this Autopsy case. + * Updates the case display name name. * - * @throws CaseActionException if there is a problem closing the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @param oldCaseName The old case name. + * @param oldPath The old path name. + * @param newCaseName The new case name. + * @param newPath The new case path. */ - 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; + 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 (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); + }); + } catch (CaseMetadataException ex) { + throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex); } } /** - * 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 + * Constructs an Autopsy case. */ - 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); - } + private Case() { } /** - * Gets the application name. + * Creates and opens a new case. * - * @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. + * @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. */ - 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; - + @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 case directory if it doesn't already exist. + * 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); } /* - * Sanitize the case name, and create a standard (single-user) or - * unique (multi-user) case database name. + * Create the case database. */ - String santizedCaseName = sanitizeCaseName(caseName); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); - Date date = new Date(); + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); 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 = santizedCaseName + "_" + dateFormat.format(date); - } - - 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.getServiceForNamespace(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.getServiceForNamespace(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); - } 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)); + 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 folders inside the case directory - String hostClause = ""; + /* + * 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); + } + open(progressIndicator); + } - if (caseType == CaseType.MULTI_USER_CASE) { - hostClause = File.separator + NetworkUtils.getLocalHostName(); + /** + * Opens an existing case. + * + * @param metadata The case metadata. + * @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 open(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException { + this.caseMetadata = metadata; + + /* + * Open the case database. + */ + try { + progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { + this.caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), metadata.getCaseDatabaseName()).toString()); + } else if (UserPreferences.getIsMultiUserModeEnabled()) { + try { + this.caseDb = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.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")); } - 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(); + } catch (TskCoreException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex); + } + open(progressIndicator); + } - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir)); + /** + * Completes the case opening tasks common to both new cases and existing + * cases. + * + * @param progressIndicator A progress indicator. + */ + @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 case-specific application service resources...", + "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...", + "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesOpenError=Could not open case resources for {0} service: {1}", + "# {0} - service name", "Case.servicesException.notificationTitle={0} Service Error" + }) + private void open(ProgressIndicator progressIndicator) { + /* + * 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.services = new Services(this.caseDb); + + /* + * Allow any registered application services to open any resources + * specific to this case. + */ + progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); + AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + try { + service.openCaseResources(context); + } catch (AutopsyService.AutopsyServiceException ex) { + /* + * The case-specific application service resources are not + * essential. Log an error and notify the user, 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()))); + } } + } - 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)); + /* + * 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"))); + } } + } - 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)); + } + + /** + * Closes the case. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.cancellingIngestJobs=Cancelling ingest jobs...", + "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(ProgressIndicator progressIndicator) { + /* + * Cancel all ingest jobs. + * + * TODO (JIRA-2227): Case closing should wait for ingest to stop to + * avoid changing the case database while ingest is still using it. + */ + progressIndicator.progress(Bundle.Case_progressMessage_cancellingIngestJobs()); + IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); + + /* + * Notify all local case event subscribers that the case is closed and + * all interactions with the current case are no longer permitted. + */ + progressIndicator.progress(Bundle.Case_progressMessage_notifyingCaseEventSubscribers()); + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), currentCase, null)); + + /* + * Stop sending/receiving case events to and from other nodes if this is + * a multi-user case. + */ + if (CaseType.MULTI_USER_CASE == currentCase.getCaseType()) { + progressIndicator.progress(Bundle.Case_progressMessage_tearingDownNetworkCommunications()); + if (null != collaborationMonitor) { + collaborationMonitor.shutdown(); } + eventPublisher.closeRemoteEventChannel(); + } - } catch (Exception e) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e); + /* + * Allow all registered applications ervices providers to close + * resources related to the case. + */ + progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources()); + AutopsyService.CaseContext context = new AutopsyService.CaseContext(currentCase, progressIndicator); + String serviceName = ""; + for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { + try { + serviceName = service.getServiceName(); + if (!serviceName.equals("Solr Keyword Search Service")) { + service.closeCaseResources(context); + } + } catch (AutopsyService.AutopsyServiceException ex) { + Case.logger.log(Level.SEVERE, String.format("%s service failed to close case resources", serviceName), ex); + } + } + + /* + * Close the case-level services. + */ + progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices()); + try { + currentCase.getServices().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()); + this.caseDb.close(); + + /* + * Disconnect the SleuthKit layer error reporter. + */ + progressIndicator.progress(Bundle.Case_progressMessage_tearingDownTskErrorReporting()); + this.caseDb.removeErrorObserver(this.sleuthkitErrorReporter); + + progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); + Logger.setLogDirectory(PlatformUtil.getLogDirectory()); + } + + /** + * 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); } } /** - * Opens an existing Autopsy case. + * 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. * @@ -1250,451 +2259,27 @@ 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 openAsCurrentCase instead. */ + @Deprecated 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.getServiceForNamespace(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.getServiceForNamespace(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); - } - } - } + openAsCurrentCase(caseMetadataFilePath); } - /** - * Gets the paths of data sources that are images. +/** + * Closes this Autopsy case. * - * @param db A case database. - * - * @return A mapping of object ids to image paths. + * @throws CaseActionException if there is a problem closing the case. The + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. + * @deprecated Use closeCurrentCase instead. */ - 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")); - } - } - - AutopsyService.CaseContext context = new AutopsyService.CaseContext(Case.currentCase, new LoggingProgressIndicator()); - String serviceName = ""; - for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { - try { - serviceName = service.getServiceName(); - service.openCaseResources(context); - } catch (AutopsyService.AutopsyServiceException ex) { - Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); - } - } - - 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); - } + @Deprecated + public void closeCase() throws CaseActionException { + closeCurrentCase(); + } - /** - * 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); - } - /** * Invokes the startup dialog window. * @@ -1750,21 +2335,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. * @@ -1777,19 +2347,6 @@ public class Case implements SleuthkitCase.ErrorObserver { 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(); - } - /** * Get relative (with respect to case dir) module output directory path * where modules should save their permanent data. The directory is a @@ -1814,20 +2371,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(); } /** @@ -1838,32 +2398,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(); } /** @@ -1881,7 +2442,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..15f9cb38a4 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,32 @@ 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; /** - * 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 +52,66 @@ 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 | ExecutionException ex) { + logger.log(Level.SEVERE, "Error closing the current case", ex); + } + 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 +119,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 +129,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 19e62db1d2..3238846a6f 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,25 +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 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) 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.caseDatabaseName = caseDatabase; createdByVersion = Version.getVersion(); createdDate = CaseMetadata.DATE_FORMAT.format(new Date()); writeToFile(); @@ -160,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. */ @@ -168,38 +174,31 @@ 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; } } - - /** - * Sets the text index name. - * - * @param caseTextIndexName The text index name. - */ - void setTextIndexName(String caseTextIndexName) throws CaseMetadataException { - String oldIndexName = caseTextIndexName; - this.textIndexName = caseTextIndexName; - try { - writeToFile(); - } catch (CaseMetadataException ex) { - this.textIndexName = oldIndexName; - throw ex; - } - } /** * Gets the case number. @@ -220,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; } } @@ -369,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); } @@ -416,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); } @@ -430,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)); @@ -437,13 +438,23 @@ 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.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_ELEMENT_NAME, true); this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, true); } + /* + * 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(); + } + /* * Update the file to the current schema, if necessary. */ @@ -497,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/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/LoggingProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java deleted file mode 100644 index c575217f06..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LoggingProgressIndicator.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2016 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.util.logging.Level; -import org.sleuthkit.autopsy.corecomponentinterfaces.ProgressIndicator; -import org.sleuthkit.autopsy.coreutils.Logger; - -/** - * A task progress indicator that writes to the Autopsy application log. - */ -class LoggingProgressIndicator implements ProgressIndicator { - - private static Logger LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); - private String taskName; - private int totalWorkUnits; - - LoggingProgressIndicator() { - LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); - } - - LoggingProgressIndicator(Logger logger) { - LOGGER = logger; - } - - @Override - public void setTaskName(String taskName) { - this.taskName = taskName; - } - - @Override - public void start(int totalWorkUnits) { - this.totalWorkUnits = totalWorkUnits; - LOGGER.log(Level.INFO, "{0} task started in determinate mode with {1} total work units", new Object[]{this.taskName, this.totalWorkUnits}); - } - - @Override - public void start() { - LOGGER.log(Level.INFO, "{0} task started in indeterminate mode", this.taskName); - } - - @Override - public void switchToIndeterminate() { - this.totalWorkUnits = 0; - LOGGER.log(Level.INFO, "{0} task switched to indeterminate mode", this.taskName); - } - - @Override - public void switchToDeterminate(int workUnitsCompleted, int totalWorkUnits) { - this.totalWorkUnits = totalWorkUnits; - LOGGER.log(Level.INFO, "{0} task switched to determinate mode, reporting {1} of {2} total work units completed", new Object[]{this.taskName, workUnitsCompleted, this.totalWorkUnits}); - } - - @Override - public void progress(String message) { - LOGGER.log(Level.INFO, "{0} task reported message '{0}'", message); - } - - @Override - public void progress(int workUnitsCompleted) { - LOGGER.log(Level.INFO, "{0} task reported {1} of {2} total work units completed", new Object[]{this.taskName, workUnitsCompleted, this.totalWorkUnits}); - } - - @Override - public void progress(String message, int workUnitsCompleted) { - LOGGER.log(Level.INFO, "{0} task reported message '{0}' with {1} of {2} total work units completed", new Object[]{this.taskName, message, workUnitsCompleted, this.totalWorkUnits}); - } - - @Override - public void finish() { - LOGGER.log(Level.INFO, "{0} task reported finished", this.taskName); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 39f831c5a1..5c7ad8aeca 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,22 +87,25 @@ final class NewCaseWizardAction extends CallableSystemAction { protected void done() { try { get(); + /* + * Run the Add Data Source wizard by invoking the Add + * Data Source wizard. + */ + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 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(); @@ -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..0637e8e90c 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)); @@ -256,7 +255,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { */ @Override public void actionPerformed(ActionEvent e) { - UpdateRecentCases.hasRecentCase = false; + UpdateRecentCases.setHasRecentCase(false); recentCases.clear(); @@ -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 31e655838d..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 @@ -194,6 +196,7 @@ public class SingleUserCaseConverter { CaseMetadata newCaseMetadata = new CaseMetadata(icd.getCaseOutputFolder().toString(), CaseType.MULTI_USER_CASE, icd.getNewCaseName(), + icd.getNewCaseName(), oldCaseMetadata.getCaseNumber(), oldCaseMetadata.getExaminer(), dbName); 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/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/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index adc89d4afa..e546e4d260 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -166,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. @@ -201,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. * @@ -227,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. @@ -262,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. * 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/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/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 20ce2db7e8..468dcf5daa 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -349,97 +349,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 items = new ArrayList<>(); - final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - items.add(new DataSources()); - items.add(new Views(tskCase)); - items.add(new Results(tskCase)); - items.add(new Tags()); - items.add(new Reports()); - contentChildren = new RootContentChildren(items); + // close the top component if there's no image in this case + if (null == currentCase || currentCase.hasData() == false) { + ((TreeView) this.jScrollPane1).setRootVisible(false); // hide the root + } else { + // if there's at least one image, load the image and open the top component + List items = new ArrayList<>(); + final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); + items.add(new DataSources()); + items.add(new Views(tskCase)); + items.add(new Results(tskCase)); + items.add(new Tags()); + items.add(new Reports()); + contentChildren = new RootContentChildren(items); - Node root = new AbstractNode(contentChildren) { - /** - * to override the right click action in the white blank - * space area on the directory tree window - */ - @Override - public Action[] getActions(boolean popup) { - return new Action[]{}; - } - - // Overide the AbstractNode use of DefaultHandle to return - // a handle which can be serialized without a parent - @Override - public Node.Handle getHandle() { - return new Node.Handle() { - @Override - public Node getNode() throws IOException { - return em.getRootContext(); - } - }; - } - }; - - root = new DirectoryTreeFilterNode(root, true); - - em.setRootContext(root); - em.getRootContext().setName(currentCase.getName()); - em.getRootContext().setDisplayName(currentCase.getName()); - ((BeanTreeView) this.jScrollPane1).setRootVisible(false); // hide the root - - // Reset the forward and back lists because we're resetting the root context - resetHistory(); - - Children childNodes = em.getRootContext().getChildren(); - TreeView tree = getTree(); - - Node results = childNodes.findChild(ResultsNode.NAME); - tree.expandNode(results); - - Children resultsChilds = results.getChildren(); - tree.expandNode(resultsChilds.findChild(KeywordHits.NAME)); - tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME)); - - Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); - showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); - showRejectedCheckBox.setSelected(false); - - Node views = childNodes.findChild(ViewsNode.NAME); - Children viewsChilds = views.getChildren(); - for (Node n : viewsChilds.getNodes()) { - tree.expandNode(n); + Node root = new AbstractNode(contentChildren) { + /** + * to override the right click action in the white blank + * space area on the directory tree window + */ + @Override + public Action[] getActions(boolean popup) { + return new Action[]{}; } - tree.collapseNode(views); - - // if the dataResult is not opened - if (!dataResult.isOpened()) { - dataResult.open(); // open the data result top component as well when the directory tree is opened + // Overide the AbstractNode use of DefaultHandle to return + // a handle which can be serialized without a parent + @Override + public Node.Handle getHandle() { + return new Node.Handle() { + @Override + public Node getNode() throws IOException { + return em.getRootContext(); + } + }; } + }; - // select the first image node, if there is one - // (this has to happen after dataResult is opened, because the event - // of changing the selected node fires a handler that tries to make - // dataResult active) - if (childNodes.getNodesCount() > 0) { - try { - em.setSelectedNodes(new Node[]{childNodes.getNodeAt(0)}); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS - } - } + root = new DirectoryTreeFilterNode(root, true); + em.setRootContext(root); + em.getRootContext().setName(currentCase.getName()); + em.getRootContext().setDisplayName(currentCase.getName()); + ((TreeView) this.jScrollPane1).setRootVisible(false); // hide the root + + // Reset the forward and back lists because we're resetting the root context + resetHistory(); + + Children childNodes = em.getRootContext().getChildren(); + TreeView tree = getTree(); + + Node results = childNodes.findChild(ResultsNode.NAME); + tree.expandNode(results); + + Children resultsChilds = results.getChildren(); + tree.expandNode(resultsChilds.findChild(KeywordHits.NAME)); + tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME)); + + Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + + Node views = childNodes.findChild(ViewsNode.NAME); + Children viewsChilds = views.getChildren(); + for (Node n : viewsChilds.getNodes()) { + tree.expandNode(n); } + + tree.collapseNode(views); + + // if the dataResult is not opened + if (!dataResult.isOpened()) { + dataResult.open(); // open the data result top component as well when the directory tree is opened + } + + // select the first image node, if there is one + // (this has to happen after dataResult is opened, because the event + // of changing the selected node fires a handler that tries to make + // dataResult active) + if (childNodes.getNodesCount() > 0) { + try { + em.setSelectedNodes(new Node[]{childNodes.getNodeAt(0)}); + } catch (PropertyVetoException ex) { + LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS + } + } + } } finally { this.setCursor(null); @@ -490,7 +494,12 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public boolean canClose() { - return !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 + /* + * Only allow the main tree view in the left side of the main window to + * be closed if there is no opne case or the open case has no data + * sources. + */ + return !Case.isCaseOpen() || Case.getCurrentCase().hasData() == false; } /** @@ -536,7 +545,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ @Override public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { String changed = evt.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case // When a case is closed, the old value of this property is the @@ -799,7 +808,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Set the selected node using a path to a previously selected node. * * @param previouslySelectedNodePath Path to a previously selected node. - * @param rootNodeName Name of the root node to match, may be null. + * @param rootNodeName Name of the root node to match, may be + * null. */ private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) { if (previouslySelectedNodePath == null) { diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java index 843934d3a8..716b63554d 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.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"); @@ -94,7 +94,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { } catch (ParseException ex) { // for now, no need to show the error message to the user here } - if (!startDateValue.equals("")) { + if (!startDateValue.isEmpty()) { if (startDate != null) { fromDate = startDate.getTimeInMillis() / 1000; // divided by 1000 because we want to get the seconds, not miliseconds } @@ -114,7 +114,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { } catch (ParseException ex) { // for now, no need to show the error message to the user here } - if (!endDateValue.equals("")) { + if (!endDateValue.isEmpty()) { if (endDate != null) { toDate = endDate.getTimeInMillis() / 1000; // divided by 1000 because we want to get the seconds, not miliseconds } @@ -166,7 +166,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { List timeZones = new ArrayList<>(); - if (Case.isCaseOpen()) { + try { // get the latest case Case currentCase = Case.getCurrentCase(); // get the most updated case @@ -195,6 +195,8 @@ class DateSearchFilter extends AbstractFileSearchFilter { String item = String.format("(GMT%+d:%02d) %s", hour, minutes, id); //NON-NLS timeZones.add(item); } + } catch (IllegalStateException ex) { + // No current case. } return timeZones; @@ -207,7 +209,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { @Override public boolean isValid() { - return this.getComponent().isValidSearch(); + return this.getComponent().isValidSearch(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java index 70c8baeaa6..603d9d1623 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java @@ -1,15 +1,15 @@ /* * 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"); * 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,14 +29,13 @@ import org.sleuthkit.autopsy.directorytree.FileSearchProvider; final class FileSearchAction extends CallableSystemAction implements FileSearchProvider { + private static final long serialVersionUID = 1L; private static FileSearchAction instance = null; FileSearchAction() { super(); - setEnabled(Case.isCaseOpen()); //no guarantee listener executed, so check here - + setEnabled(Case.isCaseOpen()); Case.addPropertyChangeListener(new PropertyChangeListener() { - @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutoIngestDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java similarity index 92% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutoIngestDataSourceProcessor.java rename to Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java index f7d50235da..e4acab8234 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutoIngestDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/framework/AutoIngestDataSourceProcessor.java @@ -16,9 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.framework; import java.nio.file.Path; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; /** * Interface implemented by DataSourceProcessors in order to be supported by diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutopsyService.java b/Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java similarity index 99% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutopsyService.java rename to Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java index 1bbc43b2c8..e4fa6958db 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/AutopsyService.java +++ b/Core/src/org/sleuthkit/autopsy/framework/AutopsyService.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.framework; import org.sleuthkit.autopsy.casemodule.Case; diff --git a/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties b/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties new file mode 100644 index 0000000000..7fcb87097c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/Bundle.properties @@ -0,0 +1,5 @@ +# 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. + +ProgressPanel.progressMessage.text=Message diff --git a/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java new file mode 100644 index 0000000000..3fcad59638 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/LoggingProgressIndicator.java @@ -0,0 +1,75 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.framework; + +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * A progress indicator that writes progress to the Autopsy application log. + */ +public final class LoggingProgressIndicator implements ProgressIndicator { + + private final Logger LOGGER = Logger.getLogger(LoggingProgressIndicator.class.getName()); + private int totalWorkUnits; + + @Override + public void start(String message, int totalWorkUnits) { + this.totalWorkUnits = totalWorkUnits; + LOGGER.log(Level.INFO, "{0} started, {1} total work units", new Object[]{message, this.totalWorkUnits}); + } + + @Override + public void start(String message) { + LOGGER.log(Level.INFO, "{0}", message); + } + + @Override + public void switchToIndeterminate(String message) { + this.totalWorkUnits = 0; + LOGGER.log(Level.INFO, "{0}", message); + } + + @Override + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { + this.totalWorkUnits = totalWorkUnits; + LOGGER.log(Level.INFO, "{0}, {1} of {2} total work units completed", new Object[]{message, workUnitsCompleted, this.totalWorkUnits}); + } + + @Override + public void progress(String message) { + LOGGER.log(Level.INFO, "{0}", message); + } + + @Override + public void progress(int workUnitsCompleted) { + LOGGER.log(Level.INFO, "{1} of {2} total work units completed", new Object[]{workUnitsCompleted, this.totalWorkUnits}); + } + + @Override + public void progress(String message, int workUnitsCompleted) { + LOGGER.log(Level.INFO, "{0}, {1} of {2} total work units completed", new Object[]{message, workUnitsCompleted, this.totalWorkUnits}); + } + + @Override + public void finish(String message) { + LOGGER.log(Level.INFO, "{0} finished", message); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java new file mode 100644 index 0000000000..f967ce1592 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/ModalDialogProgressIndicator.java @@ -0,0 +1,150 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.framework; + +import java.awt.Dialog; +import java.awt.event.ActionListener; +import javax.swing.SwingUtilities; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.util.HelpCtx; +import org.sleuthkit.autopsy.framework.ProgressIndicator; + +/** + * A progress indicator that displays progress using a modal dialog with a + * message label and a progress bar. + */ +public final class ModalDialogProgressIndicator implements ProgressIndicator { + + private final ProgressPanel progressPanel; + private final ActionListener listener; + private final Dialog dialog; + + public ModalDialogProgressIndicator(String title, Object[] options, Object initialValue, HelpCtx helpCtx, ActionListener listener) { + progressPanel = new ProgressPanel(); + this.listener = listener; + DialogDescriptor dialogDescriptor = new DialogDescriptor( + progressPanel, + title, + true, + options, + initialValue, + DialogDescriptor.BOTTOM_ALIGN, + helpCtx, + this.listener); + dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); + } + + public void setVisible(boolean isVisible) { + this.dialog.setVisible(isVisible); + } + + public ActionListener getListener() { + return listener; + } + + @Override + public void start(String message, int totalWorkUnits) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(false); + progressPanel.setMessage(message); + progressPanel.setMaximum(totalWorkUnits); + } + }); + } + + @Override + public void start(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(true); + progressPanel.setMessage(message); + } + }); + } + + @Override + public void switchToIndeterminate(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(true); + progressPanel.setMessage(message); + } + }); + } + + @Override + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setInderminate(false); + progressPanel.setMessage(message); + progressPanel.setMaximum(totalWorkUnits); + progressPanel.setCurrent(workUnitsCompleted); + } + }); + } + + @Override + public void progress(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setMessage(message); + } + }); + } + + @Override + public void progress(int workUnitsCompleted) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setCurrent(workUnitsCompleted); + } + }); + } + + @Override + public void progress(String message, int workUnitsCompleted) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setMessage(message); + progressPanel.setCurrent(workUnitsCompleted); + } + }); + } + + @Override + public void finish(String message) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressPanel.setMessage(message); + } + }); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java similarity index 77% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java rename to Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.java index 3c240c749f..0f51d98d75 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressIndicator.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"); @@ -16,52 +16,49 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.framework; /** - * An interface for task progress indicators. A progress indicator can run in + * An interface for progress indicators. A progress indicator can run in * determinate mode (the total number of work units to be completed is known) or * indeterminate mode. Switching back and forth between the two modes is - * supported. + * supported. Starting, finishing, and starting again is supported. */ public interface ProgressIndicator { - /** - * Sets the name of the task for which progress is being indicated. This - * should be set before the task is started and should not be changed. - * - * @param taskName The task name. - */ - void setTaskName(String taskName); - /** * Starts the progress indicator in determinate mode (the total number of * work units to be completed is known). * + * @param message The initial progress message. * @param totalWorkUnits The total number of work units. */ - void start(int totalWorkUnits); + void start(String message, int totalWorkUnits); /** * Starts the progress indicator in indeterminate mode (the total number of * work units to be completed is unknown). + * + * @param message The initial progress message. */ - void start(); + void start(String message); /** * Switches the progress indicator to indeterminate mode (the total number * of work units to be completed is unknown). + * @param message The initial progress message. */ - public void switchToIndeterminate(); + public void switchToIndeterminate(String message); /** * Switches the progress indicator to determinate mode (the total number of * work units to be completed is known). * + * @param message The initial progress message. * @param workUnitsCompleted The number of work units completed so far. * @param totalWorkUnits The total number of work units to be completed. */ - public void switchToDeterminate(int workUnitsCompleted, int totalWorkUnits); + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits); /** * Updates the progress indicator with a progress message. @@ -91,7 +88,9 @@ public interface ProgressIndicator { /** * Finishes the progress indicator when the task is completed. + * + * @param message The finished message. */ - void finish(); - + void finish(String message); + } diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form new file mode 100644 index 0000000000..59678117af --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.form @@ -0,0 +1,52 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java new file mode 100644 index 0000000000..6d4e46cc42 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -0,0 +1,90 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.framework; + +/** + * A progress panel consisting of a message label and a progress bar. + */ +class ProgressPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + ProgressPanel() { + initComponents(); + this.progressBar.setMinimum(0); + } + + void setMessage(String message) { + this.progressMessage.setText(message); + } + + void setInderminate(boolean indeterminate) { + this.progressBar.setIndeterminate(indeterminate); + } + + void setMaximum(int max) { + this.progressBar.setMaximum(max); + } + + void setCurrent(int current) { + this.progressBar.setValue(current); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + progressMessage = new javax.swing.JLabel(); + progressBar = new javax.swing.JProgressBar(); + + org.openide.awt.Mnemonics.setLocalizedText(progressMessage, org.openide.util.NbBundle.getMessage(ProgressPanel.class, "ProgressPanel.progressMessage.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(22, 22, 22) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(progressMessage, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 355, Short.MAX_VALUE)) + .addContainerGap(23, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(24, 24, 24) + .addComponent(progressMessage) + .addGap(18, 18, 18) + .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(33, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JProgressBar progressBar; + private javax.swing.JLabel progressMessage; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java new file mode 100644 index 0000000000..8152a097c9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/framework/SilentProgressIndicator.java @@ -0,0 +1,60 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.framework; + +import org.sleuthkit.autopsy.framework.ProgressIndicator; + +/** + * A "silent" or "null" progress indicator. + */ +public class SilentProgressIndicator implements ProgressIndicator { + + @Override + public void start(String message, int totalWorkUnits) { + } + + @Override + public void start(String message) { + } + + @Override + public void switchToIndeterminate(String message) { + } + + @Override + public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { + } + + @Override + public void progress(String message) { + } + + @Override + public void progress(int workUnitsCompleted) { + } + + @Override + public void progress(String message, int workUnitsCompleted) { + } + + @Override + public void finish(String message) { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index e1b2ae9163..40fb95c2fa 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -330,11 +330,11 @@ public class IngestManager { // check whether a multi-user case is currently being processed try { - if (!Case.isCaseOpen() || Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { + if (Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { return; } } catch (IllegalStateException ignore) { - // thorown by Case.getCurrentCase() when no case is open + // Thrown by Case.getCurrentCase() when no case is open return; } @@ -343,7 +343,7 @@ public class IngestManager { logger.log(Level.SEVERE, "Service {0} is down! Cancelling all running ingest jobs", serviceDisplayName); //NON-NLS // display notification if running interactively - if (isIngestRunning() && RuntimeProperties.coreComponentsAreActive()) { + if (isIngestRunning() && RuntimeProperties.runningWithGUI()) { EventQueue.invokeLater(new Runnable() { @Override public void run() { @@ -379,7 +379,7 @@ public class IngestManager { * Case.getTextIndexName() API. */ Case openedCase = Case.getCurrentCase(); - String channelPrefix = openedCase.getTextIndexName(); + String channelPrefix = openedCase.getName(); if (Case.CaseType.MULTI_USER_CASE == openedCase.getCaseType()) { jobEventPublisher.openRemoteEventChannel(String.format(JOB_EVENT_CHANNEL_NAME, channelPrefix)); moduleEventPublisher.openRemoteEventChannel(String.format(MODULE_EVENT_CHANNEL_NAME, channelPrefix)); @@ -398,18 +398,6 @@ public class IngestManager { clearIngestMessageBox(); } - /** - * Deprecated, use RuntimeProperties.setCoreComponentsActive instead. - * - * @param runInteractively True or false - * - * @deprecated - */ - @Deprecated - public synchronized void setRunInteractively(boolean runInteractively) { - RuntimeProperties.setCoreComponentsActive(runInteractively); - } - /** * Called by the custom installer for this package once the window system is * initialized, allowing the ingest manager to get the top component used to @@ -428,7 +416,7 @@ public class IngestManager { */ void postIngestMessage(IngestMessage message) { synchronized (this.ingestMessageBoxLock) { - if (ingestMessageBox != null && RuntimeProperties.coreComponentsAreActive()) { + if (ingestMessageBox != null && RuntimeProperties.runningWithGUI()) { if (message.getMessageType() != IngestMessage.MessageType.ERROR && message.getMessageType() != IngestMessage.MessageType.WARNING) { ingestMessageBox.displayMessage(message); } else { @@ -475,7 +463,7 @@ public class IngestManager { */ public void queueIngestJob(Collection dataSources, IngestJobSettings settings) { if (jobCreationIsEnabled) { - IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive()); + IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.runningWithGUI()); if (job.hasIngestPipeline()) { long taskId = nextThreadId.incrementAndGet(); Future task = startIngestJobsThreadPool.submit(new StartIngestJobTask(taskId, job)); @@ -497,7 +485,7 @@ public class IngestManager { */ public synchronized IngestJobStartResult beginIngestJob(Collection dataSources, IngestJobSettings settings) { if (this.jobCreationIsEnabled) { - IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive()); + IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.runningWithGUI()); if (job.hasIngestPipeline()) { return this.startIngestJob(job); // Start job } @@ -543,7 +531,7 @@ public class IngestManager { try { if (!servicesMonitor.getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).equals(ServicesMonitor.ServiceStatus.UP.toString())) { // display notification if running interactively - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { EventQueue.invokeLater(new Runnable() { @Override public void run() { @@ -582,7 +570,7 @@ public class IngestManager { logger.log(Level.SEVERE, String.format("Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.getId()), error.getThrowable()); //NON-NLS } IngestManager.logger.log(Level.SEVERE, "Ingest job {0} could not be started", job.getId()); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { final StringBuilder message = new StringBuilder(); message.append(Bundle.IngestManager_startupErr_dlgMsg()).append("\n"); message.append(Bundle.IngestManager_startupErr_dlgSolution()).append("\n\n"); @@ -966,7 +954,7 @@ public class IngestManager { return null; } - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { final String displayName = NbBundle.getMessage(this.getClass(), "IngestManager.StartIngestJobsTask.run.displayName"); this.progress = ProgressHandle.createHandle(displayName, new Cancellable() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java index fac3307653..20b791c64d 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java @@ -130,7 +130,7 @@ class IngestMessagesToolbar extends javax.swing.JPanel { Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { - setEnabled(evt.getNewValue() != null && RuntimeProperties.coreComponentsAreActive()); + setEnabled(evt.getNewValue() != null && RuntimeProperties.runningWithGUI()); } }); } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java index ebb1f8db90..49875a3997 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.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,14 +30,13 @@ import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; -@ActionID( - category = "Tools", - id = "org.sleuthkit.autopsy.ingest.RunIngestAction" -) -@ActionRegistration( - displayName = "#CTL_RunIngestAction", - lazy = false -) +/** + * The action associated with assorted Run Ingest Modules menu items. + * + * This action should only be invoked in the event dispatch thread (EDT). + */ +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.ingest.RunIngestAction") +@ActionRegistration(displayName = "#CTL_RunIngestAction", lazy = false) @Messages("CTL_RunIngestAction=Run Ingest") public final class RunIngestAction extends CallableSystemAction implements Presenter.Menu, ActionListener { @@ -51,6 +50,9 @@ public final class RunIngestAction extends CallableSystemAction implements Prese return action; } + private RunIngestAction() { + } + @Override public void performAction() { getMenuPresenter(); @@ -80,6 +82,6 @@ public final class RunIngestAction extends CallableSystemAction implements Prese @Override public boolean isEnabled() { - return Case.isCaseOpen();// && Case.getCurrentCase().hasData(); + return Case.isCaseOpen() && Case.getCurrentCase().hasData(); } } diff --git a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java index af43facec9..59c9eefef8 100644 --- a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java +++ b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.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,36 +19,42 @@ package org.sleuthkit.autopsy.keywordsearchservice; import java.io.Closeable; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; /** - * An implementation of a keyword search service. - * + * An interface for implementations of a keyword search service. + * * TODO (AUT-2158: This interface should not extend Closeable. */ public interface KeywordSearchService extends Closeable { /** - * Takes a Blackboard artifact and adds all of its attributes to the keyword - * search index. + * Tries to connect to the keyword search service server. * - * @param artifact + * @param host The hostname or IP address of the service. + * @param port The port used by the service. + * + * @throws KeywordSearchServiceException if cannot connect. + */ + public void tryConnect(String host, int port) throws KeywordSearchServiceException; + + /** + * Adds an artifact to the keyword search text index as a concantenation of + * all of its attributes. + * + * @param artifact The artifact to index. * * @throws org.sleuthkit.datamodel.TskCoreException */ public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException; /** - * Checks if we can communicate with the KeywordSearchService using the - * passed-in host and port. Closes the connection upon exit. Throws if it - * cannot communicate. + * Deletes the keyword search text index for a case. * - * @param host the remote hostname or IP address of the server - * @param port the remote port of the server - * - * @throws KeywordSearchServiceException + * @param textIndexName The text index name. */ - public void tryConnect(String host, int port) throws KeywordSearchServiceException; + public void deleteTextIndex(String textIndexName); } diff --git a/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java b/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java index 8e97875930..40ae79064c 100644 --- a/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java +++ b/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java @@ -50,10 +50,11 @@ class DataContentDynamicMenu extends JMenuItem implements DynamicMenuContent { defaultItem.addActionListener(new OpenTopComponentAction(contentWin)); - if (!Case.isCaseOpen() || Case.getCurrentCase().hasData() == false) { - defaultItem.setEnabled(false); // disable the menu items when no case is opened - } else { - defaultItem.setEnabled(true); // enable the menu items when there's a case opened / created + try { + Case currentCase = Case.getCurrentCase(); + defaultItem.setEnabled(currentCase.hasData()); + } catch (IllegalStateException ex) { + defaultItem.setEnabled(false); // disable the menu when no case is opened } comps[counter++] = defaultItem; diff --git a/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java b/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java index 11afe7cf03..16776ecdf4 100644 --- a/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java +++ b/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java @@ -53,10 +53,11 @@ class DataExplorerDynamicMenu extends JMenuItem implements DynamicMenuContent { JMenuItem item = new JMenuItem(explorerWin.getName()); item.addActionListener(new OpenTopComponentAction(explorerWin)); - if (!Case.isCaseOpen() || Case.getCurrentCase().hasData() == false) { + try { + Case currentCase = Case.getCurrentCase(); + item.setEnabled(currentCase.hasData()); + } catch (IllegalStateException ex) { item.setEnabled(false); // disable the menu when no case is opened - } else { - item.setEnabled(true); // enable the menu if the case is opened or created } comps[i++] = item; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index aea5ae1ce2..01af5addc1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -492,7 +492,7 @@ public class HashDbManager implements PropertyChangeListener { the database from HashLookupSettings and the user may not know about this because the dialogs are not being displayed. The next time user starts Autopsy, HashDB will load without errors and the user may think that the problem was solved.*/ - if (!allDatabasesLoadedCorrectly && RuntimeProperties.coreComponentsAreActive()) { + if (!allDatabasesLoadedCorrectly && RuntimeProperties.runningWithGUI()) { try { HashLookupSettings.writeSettings(new HashLookupSettings(this.knownHashSets, this.knownBadHashSets)); allDatabasesLoadedCorrectly = true; @@ -512,7 +512,7 @@ public class HashDbManager implements PropertyChangeListener { // Give the user an opportunity to find the desired file. String newPath = null; - if (RuntimeProperties.coreComponentsAreActive() && + if (RuntimeProperties.runningWithGUI() && JOptionPane.showConfirmDialog(null, NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc", hashSetName, configuredPath), diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java index b490099b57..d7471df562 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.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"); @@ -36,13 +36,13 @@ import org.sleuthkit.autopsy.corecomponents.AdvancedConfigurationCleanDialog; */ class HashDbPanelSearchAction extends CallableSystemAction { + private static final long serialVersionUID = 1L; static final String ACTION_NAME = NbBundle.getMessage(HashDbPanelSearchAction.class, "HashDbPanelSearchAction.actionName"); private static HashDbPanelSearchAction instance = null; HashDbPanelSearchAction() { super(); - setEnabled(Case.isCaseOpen()); //no guarantee listener executed, so check here - + setEnabled(Case.isCaseOpen()); Case.addPropertyChangeListener(new PropertyChangeListener() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java index ae52f86219..9adc3a4df0 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettings.java @@ -203,7 +203,7 @@ final class HashLookupSettings implements Serializable { newHashSetName = hashSetName + suffix; } while (hashSetNames.contains(newHashSetName)); logger.log(Level.INFO, "Duplicate hash set name " + hashSetName + " found. Replacing with " + newHashSetName + "."); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.replacingDuplicateHashsetNameMsg", @@ -269,7 +269,7 @@ final class HashLookupSettings implements Serializable { try { FileUtils.copyFile(new File(configFilePath), new File(backupFilePath)); logger.log(Level.INFO, "Updated the schema, backup saved at: " + backupFilePath); - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.savedBackupOfOldConfigMsg", diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index c3aba3434f..c1cffc9243 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -301,7 +301,7 @@ class ReportExcel implements TableReportModule { row = sheet.createRow(rowIndex); row.setRowStyle(setStyle); row.createCell(0).setCellValue(NbBundle.getMessage(this.getClass(), "ReportExcel.cellVal.caseName")); - row.createCell(1).setCellValue(currentCase.getName()); + row.createCell(1).setCellValue(currentCase.getDisplayName()); ++rowIndex; row = sheet.createRow(rowIndex); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 6f32fd2f65..afad0d5746 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -93,7 +93,7 @@ class ReportGenerator { DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); Date date = new Date(); String dateNoTime = dateFormat.format(date); - this.reportPath = currentCase.getReportDirectory() + File.separator + currentCase.getName() + " " + dateNoTime + File.separator; + this.reportPath = currentCase.getReportDirectory() + File.separator + currentCase.getDisplayName() + " " + dateNoTime + File.separator; this.errorList = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 3a7da57e3f..4b643e2a02 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -857,7 +857,7 @@ class ReportHTML implements TableReportModule { iconPath = "favicon.ico"; } index.append("\n").append(reportTitle).append(" ").append( - NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getName())).append( + NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append( "\n"); //NON-NLS index.append("\n"); //NON-NLS @@ -1017,7 +1017,7 @@ class ReportHTML implements TableReportModule { Date date = new Date(); String datetime = datetimeFormat.format(date); - String caseName = currentCase.getName(); + String caseName = currentCase.getDisplayName(); String caseNumber = currentCase.getNumber(); String examiner = currentCase.getExaminer(); int imagecount; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 5e32ce5016..75c8f7c205 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -84,7 +84,7 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { Case newCase = (Case) evt.getNewValue(); - setEnabled(newCase != null && RuntimeProperties.coreComponentsAreActive()); + setEnabled(newCase != null && RuntimeProperties.runningWithGUI()); } }); diff --git a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java index 3348fdcafd..1de1db7eaa 100755 --- a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java +++ b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java @@ -90,7 +90,7 @@ public class AddTaggedHashesToHashDb implements GeneralReportModule { if (content instanceof AbstractFile) { if (null != ((AbstractFile) content).getMd5Hash()) { try { - hashSet.addHashes(tag.getContent(), Case.getCurrentCase().getName()); + hashSet.addHashes(tag.getContent(), Case.getCurrentCase().getDisplayName()); } catch (TskCoreException ex) { Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Error adding hash for obj_id = " + tag.getContent().getId() + " to hash database " + hashSet.getHashSetName(), ex); failedExports.add(tag.getContent().getName()); diff --git a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModule.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModule.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModule.java rename to Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModule.java index b3dafaa1d6..975bc1e11c 100644 --- a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModule.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"); @@ -16,12 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.report.testfixtures; +package org.sleuthkit.autopsy.test; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.xml.bind.DatatypeConverter; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; @@ -31,7 +32,6 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; -import org.openide.util.NbBundle; /** * A file ingest module that associates custom artifacts and attributes with diff --git a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModuleFactory.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModuleFactory.java rename to Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModuleFactory.java index 766a3260dd..0b0eb7d4d2 100644 --- a/Core/src/org/sleuthkit/autopsy/report/testfixtures/CustomArtifactsCreatorIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactsCreatorIngestModuleFactory.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"); @@ -16,12 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.report.testfixtures; +package org.sleuthkit.autopsy.test; -import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.ingest.FileIngestModule; -import org.sleuthkit.autopsy.ingest.IngestModuleFactory; import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; diff --git a/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java new file mode 100644 index 0000000000..0cd8897ff7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/test/TestAutopsyService.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.test; + +import java.util.logging.Level; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.framework.AutopsyService; +import org.sleuthkit.autopsy.framework.ProgressIndicator; + +/** + * An implementation of the Autopsy service interface used for test purposes. + */ +//@ServiceProvider(service = AutopsyService.class) +public class TestAutopsyService implements AutopsyService { + + private static final Logger LOGGER = Logger.getLogger(TestAutopsyService.class.getName()); + + @Override + public String getServiceName() { + return "Test Autopsy Service"; + } + + @Override + public void openCaseResources(CaseContext context) throws AutopsyServiceException { + ProgressIndicator progressIndicator = context.getProgressIndicator(); + try { + progressIndicator.start("Doing first task...", 100); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + Thread.sleep(1000L); + progressIndicator.progress(10); + progressIndicator.finish("First task completed."); + progressIndicator.start("Doing second task..."); + Thread.sleep(10000L); + progressIndicator.finish("Second task completed."); + } catch (InterruptedException ex) { + LOGGER.log(Level.INFO, "Autopsy Test Service caught interrupt while working"); + if (context.cancelRequested()) { + progressIndicator.finish("Cancelling..."); + try { + Thread.sleep(1000L); + } catch (InterruptedException ex1) { + LOGGER.log(Level.INFO, "Autopsy Test Service caught interrupt while working"); + } + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 04fd16c71d..2982f383dd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -58,8 +58,7 @@ public final class OpenTimelineAction extends CallableSystemAction implements Pr private static TimeLineController timeLineController = null; private final JButton toolbarButton = new JButton(getName(), - new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS - + new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS /** * Invalidate the reference to the controller so that a new one will be diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 9ad1760ef7..2c5a6472ee 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -453,8 +453,8 @@ public class TimeLineController { TimeLineController.this.showFullRange(); } else { //prompt user to pick specific event and time range - ShowInTimelineDialog showInTimelineDilaog = - (file == null) + ShowInTimelineDialog showInTimelineDilaog + = (file == null) ? new ShowInTimelineDialog(TimeLineController.this, artifact) : new ShowInTimelineDialog(TimeLineController.this, file); Optional dialogResult = showInTimelineDilaog.showAndWait(); @@ -575,7 +575,6 @@ public class TimeLineController { Case.addPropertyChangeListener(caseListener); listeningToAutopsy = true; } - Platform.runLater(() -> promptForRebuild(file, artifact)); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index 7f0bf14f3a..04117963c7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -100,7 +100,7 @@ public class SaveSnapshotAsReport extends Action { setEventHandler(actionEvent -> { //capture generation date and use to make default report name Date generationDate = new Date(); - final String defaultReportName = FileUtil.escapeFileName(currentCase.getName() + " " + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); //NON_NLS + final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); //NON_NLS BufferedImage snapshot = SwingFXUtils.fromFXImage(nodeSupplier.get().snapshot(null, null), null); //prompt user to pick report name diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java index 646191de39..6d37ca2a26 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java @@ -46,7 +46,6 @@ class AutoIngestCase implements Comparable { * * @param caseDirectoryPath The case directory path. */ - // RJCTODO: Throw instead of reporting error, let client decide what to do. AutoIngestCase(Path caseDirectoryPath) { this.caseDirectoryPath = caseDirectoryPath; caseName = PathUtils.caseNameFromCaseDirectoryPath(caseDirectoryPath); @@ -100,7 +99,6 @@ class AutoIngestCase implements Comparable { * * @return The last accessed date. */ - // RJCTODO: Throw instead of reporting error, let client decide what to do. Date getLastAccessedDate() { try { BasicFileAttributes fileAttrs = Files.readAttributes(metadataFilePath, BasicFileAttributes.class); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java index ab559de58e..3d3ff8951f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java @@ -45,20 +45,10 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa this.nodeName = nodeName; } - /** - * RJCTODO - * - * @return - */ String getCaseName() { return caseName; } - /** - * RJCTODO - * - * @return - */ String getNodeName() { return nodeName; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java new file mode 100644 index 0000000000..1b95cd87be --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseManager.java @@ -0,0 +1,123 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.swing.SwingUtilities; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.casemodule.AddImageAction; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseNewAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; + +/** + * Handles locating and opening cases created by auto ingest. + */ +final class AutoIngestCaseManager { + + private static final Logger LOGGER = Logger.getLogger(AutoIngestCaseManager.class.getName()); + private static AutoIngestCaseManager instance; + + /** + * Gets the auto ingest case manager. + * + * @return The auto ingest case manager singleton. + */ + synchronized static AutoIngestCaseManager getInstance() { + if (null == instance) { + instance = new AutoIngestCaseManager(); + } + return instance; + } + + /** + * Constructs an object that handles locating and opening cases created by + * auto ingest. + */ + private AutoIngestCaseManager() { + /* + * Disable the new case action because review mode is only for looking + * at cases created by automated ingest. + */ + CallableSystemAction.get(CaseNewAction.class).setEnabled(false); + + /* + * Permanently delete the "Open Recent Cases" item in the "File" menu. + * This is quite drastic, as it also affects Autopsy standalone mode on + * this machine, but review mode is only for looking at cases created by + * automated ingest. + */ + FileObject root = FileUtil.getConfigRoot(); + FileObject openRecentCasesMenu = root.getFileObject("Menu/Case/OpenRecentCase"); + if (openRecentCasesMenu != null) { + try { + openRecentCasesMenu.delete(); + } catch (IOException ex) { + AutoIngestCaseManager.LOGGER.log(Level.WARNING, "Unable to remove Open Recent Cases file menu item", ex); + } + } + } + + /* + * Gets a list of the cases in the top level case folder used by auto + * ingest. + */ + List getCases() { + List cases = new ArrayList<>(); + List caseFolders = PathUtils.findCaseFolders(Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder())); + for (Path caseFolderPath : caseFolders) { + cases.add(new AutoIngestCase(caseFolderPath)); + } + return cases; + } + + /** + * Opens an auto ingest case case. + * + * @param caseMetadataFilePath Path to the case metadata file. + * + * @throws CaseActionException + */ + synchronized void openCase(Path caseMetadataFilePath) throws CaseActionException { + /* + * Open the case. + */ + Case.openAsCurrentCase(caseMetadataFilePath.toString()); + + /** + * Disable the add data source action in auto ingest examiner mode. This + * has to be done here because Case.open() calls Case.doCaseChange() and + * the latter method enables the action. Since Case.doCaseChange() + * enables the menus on EDT by calling SwingUtilities.invokeLater(), we + * have to do the same thing here to maintain the order of execution. + */ + SwingUtilities.invokeLater(() -> { + CallableSystemAction.get(AddImageAction.class).setEnabled(false); + }); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form similarity index 96% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.form rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form index 2756fa8da0..0c22854c9a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form @@ -73,7 +73,7 @@ - + @@ -109,7 +109,7 @@ - + @@ -160,7 +160,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -186,7 +186,7 @@ - + @@ -199,7 +199,7 @@ - + @@ -213,7 +213,7 @@ - + @@ -222,10 +222,10 @@ - + - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java similarity index 88% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java index fc45308db7..22b2c83a38 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCasePanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.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,38 +18,38 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.Cursor; import java.awt.Desktop; -import java.nio.file.Paths; -import java.util.List; -import javax.swing.JPanel; import java.awt.EventQueue; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Date; +import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.JOptionPane; import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; -import org.sleuthkit.autopsy.casemodule.StartupWindowProvider; -import java.awt.Cursor; -import java.io.IOException; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.experimental.autoingest.ReviewModeCaseManager.ReviewModeCaseManagerException; +import org.sleuthkit.autopsy.casemodule.StartupWindowProvider; +import org.sleuthkit.autopsy.coreutils.Logger; /** - * A panel that allows a user to open cases created by automated ingest. + * A panel that allows a user to open cases created by auto ingest. */ -public final class ReviewModeCasePanel extends JPanel { +public final class AutoIngestCasePanel extends JPanel { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(ReviewModeCasePanel.class.getName()); + private static final Logger logger = Logger.getLogger(AutoIngestCasePanel.class.getName()); private static final AutoIngestCase.LastAccessedDateDescendingComparator reverseDateModifiedComparator = new AutoIngestCase.LastAccessedDateDescendingComparator(); private static final int CASE_COL_MIN_WIDTH = 30; private static final int CASE_COL_MAX_WIDTH = 2000; @@ -60,8 +60,8 @@ public final class ReviewModeCasePanel extends JPanel { private static final int STATUS_COL_MIN_WIDTH = 55; private static final int STATUS_COL_MAX_WIDTH = 250; private static final int STATUS_COL_PREFERRED_WIDTH = 60; - private static final int MILLISECONDS_TO_WAIT_BEFORE_STARTING = 500; // RJCTODO: Shorten name - private static final int MILLISECONDS_TO_WAIT_BETWEEN_UPDATES = 30000; // RJCTODO: Shorten name + private static final int MILLIS_TO_WAIT_BEFORE_STARTING = 500; + private static final int MILLIS_TO_WAIT_BETWEEN_UPDATES = 30000; private ScheduledThreadPoolExecutor casesTableRefreshExecutor; /* @@ -71,11 +71,11 @@ public final class ReviewModeCasePanel extends JPanel { * TODO (RC): Consider unifying this stuff in an enum as in * AutoIngestDashboard to make it less error prone. */ - private static final String CASE_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.CaseHeaderText"); - private static final String CREATEDTIME_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); - private static final String COMPLETEDTIME_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.LastAccessedTimeHeaderText"); - private static final String STATUS_ICON_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); - private static final String OUTPUT_FOLDER_HEADER = org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); + private static final String CASE_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.CaseHeaderText"); + private static final String CREATEDTIME_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); + private static final String COMPLETEDTIME_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.LastAccessedTimeHeaderText"); + private static final String STATUS_ICON_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); + private static final String OUTPUT_FOLDER_HEADER = org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); enum COLUMN_HEADERS { @@ -83,7 +83,7 @@ public final class ReviewModeCasePanel extends JPanel { CREATEDTIME, COMPLETEDTIME, STATUS_ICON, - OUTPUTFOLDER // RJCTODO: Change name + OUTPUTFOLDER } private final String[] columnNames = {CASE_HEADER, CREATEDTIME_HEADER, COMPLETEDTIME_HEADER, STATUS_ICON_HEADER, OUTPUT_FOLDER_HEADER}; private DefaultTableModel caseTableModel; @@ -92,8 +92,10 @@ public final class ReviewModeCasePanel extends JPanel { /** * Constructs a panel that allows a user to open cases created by automated * ingest. + * + * @param parent The parent dialog for this panel. */ - public ReviewModeCasePanel(JDialog parent) { + public AutoIngestCasePanel(JDialog parent) { caseTableModel = new DefaultTableModel(columnNames, 0) { private static final long serialVersionUID = 1L; @@ -183,7 +185,7 @@ public final class ReviewModeCasePanel extends JPanel { casesTableRefreshExecutor = new ScheduledThreadPoolExecutor(1); this.casesTableRefreshExecutor.scheduleAtFixedRate(() -> { refreshCasesTable(); - }, MILLISECONDS_TO_WAIT_BEFORE_STARTING, MILLISECONDS_TO_WAIT_BETWEEN_UPDATES, TimeUnit.MILLISECONDS); + }, MILLIS_TO_WAIT_BEFORE_STARTING, MILLIS_TO_WAIT_BETWEEN_UPDATES, TimeUnit.MILLISECONDS); } } @@ -214,7 +216,7 @@ public final class ReviewModeCasePanel extends JPanel { private void refreshCasesTable() { try { currentlySelectedCase = getSelectedCase(); - List theModel = ReviewModeCaseManager.getInstance().getCases(); + List theModel = AutoIngestCaseManager.getInstance().getCases(); EventQueue.invokeLater(new CaseTableRefreshTask(theModel)); } catch (Exception ex) { logger.log(Level.SEVERE, "Unexpected exception in refreshCasesTable", ex); //NON-NLS @@ -279,10 +281,10 @@ public final class ReviewModeCasePanel extends JPanel { private void openCase(Path caseMetadataFilePath) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - ReviewModeCaseManager.getInstance().openCaseInEDT(caseMetadataFilePath); + AutoIngestCaseManager.getInstance().openCase(caseMetadataFilePath); stopCasesTableRefreshes(); StartupWindowProvider.getInstance().close(); - } catch (ReviewModeCaseManagerException ex) { + } catch (CaseActionException ex) { logger.log(Level.SEVERE, String.format("Error while opening case with case metadata file path %s", caseMetadataFilePath), ex); /* * ReviewModeCaseManagerExceptions have user-friendly error @@ -290,7 +292,7 @@ public final class ReviewModeCasePanel extends JPanel { */ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage(), - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.cannotOpenCase"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "ReviewModeCasePanel.cannotOpenCase"), JOptionPane.ERROR_MESSAGE); } finally { @@ -345,18 +347,12 @@ public final class ReviewModeCasePanel extends JPanel { long multiplier = 1; if (rbAllCases.isSelected()) { return true; - } else { - if (rbMonths.isSelected()) { - multiplier = 31; - } else { - if (rbWeeks.isSelected()) { - multiplier = 7; - } else { - if (rbDays.isSelected()) { - multiplier = 1; - } - } - } + } else if (rbMonths.isSelected()) { + multiplier = 31; + } else if (rbWeeks.isSelected()) { + multiplier = 7; + } else if (rbDays.isSelected()) { + multiplier = 1; } return ((currentTime - inputTime) / (1000 * 60 * 60 * 24)) < (numberOfUnits * multiplier); } @@ -387,7 +383,7 @@ public final class ReviewModeCasePanel extends JPanel { setName("Completed Cases"); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnOpen.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnOpen.text")); // NOI18N bnOpen.setEnabled(false); bnOpen.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -407,7 +403,7 @@ public final class ReviewModeCasePanel extends JPanel { }); scrollPaneTable.setViewportView(casesTable); - org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnRefresh.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnRefresh.text")); // NOI18N bnRefresh.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnRefreshActionPerformed(evt); @@ -416,7 +412,7 @@ public final class ReviewModeCasePanel extends JPanel { rbGroupHistoryLength.add(rbAllCases); rbAllCases.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(rbAllCases, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbAllCases.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbAllCases, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbAllCases.text")); // NOI18N rbAllCases.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { rbAllCasesItemStateChanged(evt); @@ -424,7 +420,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupHistoryLength.add(rbMonths); - org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbMonths.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbMonths.text")); // NOI18N rbMonths.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { rbMonthsItemStateChanged(evt); @@ -432,7 +428,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupHistoryLength.add(rbWeeks); - org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbWeeks.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbWeeks.text")); // NOI18N rbWeeks.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { rbWeeksItemStateChanged(evt); @@ -440,7 +436,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupHistoryLength.add(rbDays); - org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbDays.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbDays.text")); // NOI18N rbDays.setName(""); // NOI18N rbDays.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { @@ -449,7 +445,7 @@ public final class ReviewModeCasePanel extends JPanel { }); rbGroupLabel.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.rbGroupLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbGroupLabel.text")); // NOI18N javax.swing.GroupLayout panelFilterLayout = new javax.swing.GroupLayout(panelFilter); panelFilter.setLayout(panelFilterLayout); @@ -481,8 +477,8 @@ public final class ReviewModeCasePanel extends JPanel { .addContainerGap()) ); - org.openide.awt.Mnemonics.setLocalizedText(bnShowLog, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnShowLog.text")); // NOI18N - bnShowLog.setToolTipText(org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "ReviewModeCasePanel.bnShowLog.toolTipText")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnShowLog, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnShowLog.text")); // NOI18N + bnShowLog.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnShowLog.toolTipText")); // NOI18N bnShowLog.setEnabled(false); bnShowLog.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -586,14 +582,14 @@ public final class ReviewModeCasePanel extends JPanel { if (pathToLog.toFile().exists()) { Desktop.getDesktop().edit(pathToLog.toFile()); } else { - JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.cannotFindLog"), - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.cannotFindLog"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); } } catch (IOException ex) { logger.log(Level.SEVERE, String.format("Error attempting to open case auto ingest log file %s", pathToLog), ex); JOptionPane.showMessageDialog(this, - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.cannotOpenLog"), - org.openide.util.NbBundle.getMessage(ReviewModeCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.cannotOpenLog"), + org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.PLAIN_MESSAGE); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 0687bc9c2e..5fbf380601 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; * Event published when an automated ingest manager prioritizes all or part of a * case. */ -public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { // RJCTODO: Rename to AutoIngestPrioritizationEvent +public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { private static final long serialVersionUID = 1L; private final String caseName; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index ada7f61206..8372205fc6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -24,6 +24,7 @@ import java.nio.file.Paths; import java.time.Instant; import java.util.Comparator; import java.util.Date; +import java.util.Objects; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; @@ -55,9 +56,9 @@ public final class AutoIngestJob implements Comparable, Serializa @GuardedBy("this") transient private IngestJob ingestJob; @GuardedBy("this") - transient private boolean cancelled; // RJCTODO: Document + transient private boolean cancelled; @GuardedBy("this") - transient private boolean completed; // RJCTODO: Document + transient private boolean completed; @GuardedBy("this") private Date completedDate; @GuardedBy("this") @@ -81,7 +82,6 @@ public final class AutoIngestJob implements Comparable, Serializa * indicate the the job is not completed, i.e., new * Date(0L). */ - // RJCTODO: The null case directory is error-prone and the nodeName is confusing. AutoIngestJob(Manifest manifest, Path caseDirectoryPath, int priority, String nodeName, Stage stage, Date completedDate, boolean errorsOccurred) { this.manifest = manifest; if (null != caseDirectoryPath) { @@ -112,7 +112,6 @@ public final class AutoIngestJob implements Comparable, Serializa * * @return True or false */ - // RJCTODO: Use this or lose this synchronized boolean hasCaseDirectoryPath() { return (false == this.caseDirectoryPath.isEmpty()); } @@ -161,21 +160,10 @@ public final class AutoIngestJob implements Comparable, Serializa return this.priority; } - /** - * RJCTODO - * - * @param newStage - */ synchronized void setStage(Stage newStage) { setStage(newStage, Date.from(Instant.now())); } - /** - * RJCTODO - * - * @param state - * @param stateStartedDate - */ synchronized void setStage(Stage newState, Date stateStartedDate) { if (Stage.CANCELLING == this.stage && Stage.COMPLETED != newState) { return; @@ -184,29 +172,14 @@ public final class AutoIngestJob implements Comparable, Serializa this.stageStartDate = stateStartedDate; } - /** - * RJCTODO: - * - * @return - */ synchronized Stage getStage() { return this.stage; } - /** - * RJCTODO - * - * @return - */ synchronized Date getStageStartDate() { return this.stageStartDate; } - /** - * RJCTODO - * - * @return - */ synchronized StageDetails getStageDetails() { String description; Date startDate; @@ -223,7 +196,7 @@ public final class AutoIngestJob implements Comparable, Serializa if (!ingestModuleHandle.isCancelled()) { description = ingestModuleHandle.displayName(); } else { - description = String.format(Stage.CANCELLING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); // RJCTODO: FIx this + description = String.format(Stage.CANCELLING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); } } else { /** @@ -248,26 +221,14 @@ public final class AutoIngestJob implements Comparable, Serializa this.dataSourceProcessor = dataSourceProcessor; } - /** - * RJCTODO - */ - // RJCTODO: Consider moving this class into AIM and making this private synchronized void setIngestJob(IngestJob ingestJob) { this.ingestJob = ingestJob; } - /** - * RJCTODO - */ - // RJCTODO: Consider moving this class into AIM and making this private. - // Or move the AID into a separate package. Or do not worry about it. synchronized IngestJob getIngestJob() { return this.ingestJob; } - /** - * RJCTODO - */ synchronized void cancel() { setStage(Stage.CANCELLING); cancelled = true; @@ -280,26 +241,15 @@ public final class AutoIngestJob implements Comparable, Serializa } } - /** - * RJCTODO - */ synchronized boolean isCancelled() { return cancelled; } - /** - * RJCTODO - */ synchronized void setCompleted() { setStage(Stage.COMPLETED); completed = true; } - /** - * RJCTODO - * - * @return - */ synchronized boolean isCompleted() { return completed; } @@ -321,7 +271,7 @@ public final class AutoIngestJob implements Comparable, Serializa * @return True or false. */ synchronized Date getCompletedDate() { - return completedDate; // RJCTODO: Consider returning null if == 0 (epoch) + return completedDate; } /** @@ -342,23 +292,10 @@ public final class AutoIngestJob implements Comparable, Serializa return this.errorsOccurred; } - /** - * RJCTODO Gets name of the node associated with the job, possibly a remote - * hose if the job is in progress. - * - * @return The node name. - */ String getNodeName() { return nodeName; } - /** - * RJCTODO - * - * @param obj - * - * @return - */ @Override public boolean equals(Object obj) { if (!(obj instanceof AutoIngestJob)) { @@ -370,26 +307,12 @@ public final class AutoIngestJob implements Comparable, Serializa return this.getManifest().getFilePath().equals(((AutoIngestJob) obj).getManifest().getFilePath()); } - /** - * RJCTODO - * - * @return - */ @Override public int hashCode() { - // RJCTODO: Update this - int hash = 7; -// hash = 71 * hash + Objects.hashCode(this.dateCreated); + int hash = 71 * (Objects.hashCode(this.caseDirectoryPath)); return hash; } - /** - * RJCTODO Default sorting is by ready file creation date, descending - * - * @param o - * - * @return - */ @Override public int compareTo(AutoIngestJob o) { return -this.getManifest().getDateFileCreated().compareTo(o.getManifest().getDateFileCreated()); @@ -401,14 +324,6 @@ public final class AutoIngestJob implements Comparable, Serializa */ static class ReverseDateCompletedComparator implements Comparator { - /** - * RJCTODO - * - * @param o1 - * @param o2 - * - * @return - */ @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { return -o1.getStageStartDate().compareTo(o2.getStageStartDate()); @@ -420,14 +335,6 @@ public final class AutoIngestJob implements Comparable, Serializa */ public static class PriorityComparator implements Comparator { - /** - * RJCTODO - * - * @param job - * @param anotherJob - * - * @return - */ @Override public int compare(AutoIngestJob job, AutoIngestJob anotherJob) { return -(job.getPriority().compareTo(anotherJob.getPriority())); @@ -442,14 +349,6 @@ public final class AutoIngestJob implements Comparable, Serializa */ static class AlphabeticalComparator implements Comparator { - /** - * RJCTODO - * - * @param o1 - * @param o2 - * - * @return - */ @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { if (o1.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { @@ -462,10 +361,6 @@ public final class AutoIngestJob implements Comparable, Serializa } } - /** - * RJCTODO - */ - // RJCTODO: Combine this enum with StageDetails to make a single class. enum Stage { PENDING("Pending"), @@ -494,40 +389,21 @@ public final class AutoIngestJob implements Comparable, Serializa } - /** - * RJCTODO - */ @Immutable static final class StageDetails { private final String description; private final Date startDate; - /** - * RJCTODO - * - * @param description - * @param startDate - */ private StageDetails(String description, Date startDate) { this.description = description; this.startDate = startDate; } - /** - * RJCTODO - * - * @return - */ String getDescription() { return this.description; } - /** - * RJCTODO - * - * @return - */ Date getStartDate() { return this.startDate; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java index e1c9464d00..55248e0a9c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobEvent.java @@ -22,28 +22,17 @@ import java.io.Serializable; import javax.annotation.concurrent.Immutable; import org.sleuthkit.autopsy.events.AutopsyEvent; -/** - * RJCTODO - */ @Immutable abstract class AutoIngestJobEvent extends AutopsyEvent implements Serializable { private static final long serialVersionUID = 1L; private final AutoIngestJob job; - /** - * RJCTODO - * - */ AutoIngestJobEvent(AutoIngestManager.Event eventSubType, AutoIngestJob job) { super(eventSubType.toString(), null, null); this.job = job; } - /** - * RJCTODO - * @return - */ AutoIngestJob getJob() { return this.job; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index e9406950fd..161a20286e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -192,7 +192,7 @@ final class AutoIngestJobLogger { * to acquire an exclusive lock on the * log file. */ - void logDataSourceProcessorCancelled() throws AutoIngestJobLoggerException, InterruptedException { // RJCTODO: Is this used now? + void logDataSourceProcessorCancelled() throws AutoIngestJobLoggerException, InterruptedException { log(MessageCategory.WARNING, "Cancelled adding data source to case"); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java index de2ef46ffd..b80157b3b4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStartedEvent.java @@ -28,9 +28,6 @@ public final class AutoIngestJobStartedEvent extends AutoIngestJobEvent implemen private static final long serialVersionUID = 1L; - /** - * RJCTODO - */ public AutoIngestJobStartedEvent(AutoIngestJob job) { super(AutoIngestManager.Event.JOB_STARTED, job); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java index 23a444f7d7..153cc04ced 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobStatusEvent.java @@ -28,9 +28,6 @@ public final class AutoIngestJobStatusEvent extends AutoIngestJobEvent implement private static final long serialVersionUID = 1L; - /** - * RJCTODO - */ public AutoIngestJobStatusEvent(AutoIngestJob job) { super(AutoIngestManager.Event.JOB_STATUS_UPDATED, job); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index bb55281227..eb45ae0df4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.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,11 +18,9 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; import java.io.File; import java.io.IOException; import static java.nio.file.FileVisitOption.FOLLOW_LINKS; @@ -38,9 +36,6 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import org.sleuthkit.autopsy.modules.vmextractor.VirtualMachineFinder; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.datamodel.CaseDbConnectionInfo; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -54,13 +49,13 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.Observable; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -69,65 +64,50 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; -import javax.swing.filechooser.FileFilter; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import org.apache.commons.io.FilenameUtils; +import org.apache.solr.client.solrj.impl.HttpSolrServer; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.openide.modules.InstalledFileLocator; import org.sleuthkit.autopsy.casemodule.Case.CaseType; -import org.sleuthkit.autopsy.casemodule.GeneralFilter; -import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; -import org.sleuthkit.autopsy.core.RuntimeProperties; -import org.sleuthkit.autopsy.core.ServicesMonitor; -import org.sleuthkit.autopsy.core.UserPreferencesException; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; -import org.sleuthkit.autopsy.coreutils.ExecUtil; -import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.events.AutopsyEvent; -import org.sleuthkit.autopsy.events.AutopsyEventPublisher; -import org.sleuthkit.autopsy.ingest.IngestJob; -import org.sleuthkit.autopsy.ingest.IngestJobSettings; -import org.sleuthkit.datamodel.Content; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; -import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; -import org.apache.solr.client.solrj.impl.HttpSolrServer; -import org.openide.util.Lookup; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; +import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.core.ServicesMonitor.ServicesMonitorException; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.framework.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; -import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.events.AutopsyEventException; -import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; -import org.sleuthkit.autopsy.ingest.IngestJobStartResult; -import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.events.AutopsyEventPublisher; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAlertFile.AutoIngestAlertFileException; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobLogger.AutoIngestJobLoggerException; import org.sleuthkit.autopsy.experimental.autoingest.FileExporter.FileExportException; import org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException; import org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus; -import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PENDING; -import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PROCESSING; import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.COMPLETED; import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.DELETED; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; -import org.sleuthkit.autopsy.coreutils.FileUtil; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAlertFile.AutoIngestAlertFileException; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobLogger.AutoIngestJobLoggerException; +import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PENDING; +import static org.sleuthkit.autopsy.experimental.autoingest.ManifestNodeData.ProcessingStatus.PROCESSING; +import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; +import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException; +import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobStartResult; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.Content; /** * An auto ingest manager is responsible for processing auto ingest jobs defined @@ -240,7 +220,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang eventPublisher.openRemoteEventChannel(EVENT_CHANNEL_NAME); SYS_LOGGER.log(Level.INFO, "Opened auto ingest event channel"); } catch (AutopsyEventException ex) { - throw new AutoIngestManagerStartupException("Failed to open aut ingest event channel", ex); + SYS_LOGGER.log(Level.SEVERE, "Failed to open auto ingest event channel", ex); + throw new AutoIngestManagerStartupException("Failed to open auto ingest event channel", ex); } rootInputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeImageFolder()); rootOutputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder()); @@ -249,7 +230,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); jobStatusPublishingExecutor.scheduleAtFixedRate(new PeriodicJobStatusEventTask(), JOB_STATUS_EVENT_INTERVAL_SECONDS, JOB_STATUS_EVENT_INTERVAL_SECONDS, TimeUnit.SECONDS); eventPublisher.addSubscriber(EVENT_LIST, instance); - RuntimeProperties.setCoreComponentsActive(false); + try { + RuntimeProperties.setRunningWithGUI(false); + SYS_LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false"); + } catch (RuntimeProperties.RuntimePropertiesException ex) { + SYS_LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex); + throw new AutoIngestManagerStartupException("Failed to set running with desktop GUI runtime property to false", ex); + } state = State.RUNNING; errorState = ErrorState.NONE; } @@ -483,7 +470,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } for (AutoIngestJob job : hostNamesToRunningJobs.values()) { runningJobs.add(job); - runningJobs.sort(new AutoIngestJob.AlphabeticalComparator()); // RJCTODO: This sort should be done in the AID + runningJobs.sort(new AutoIngestJob.AlphabeticalComparator()); } } if (null != completedJobs) { @@ -502,12 +489,12 @@ public final class AutoIngestManager extends Observable implements PropertyChang } inputScanExecutor.submit(new InputDirScanTask()); } - + /** * Start a scan of the input directories and wait for scan to complete. */ - void scanInputDirsAndWait(){ - if (State.RUNNING != state) { + void scanInputDirsAndWait() { + if (State.RUNNING != state) { return; } SYS_LOGGER.log(Level.INFO, "Starting input scan of {0}", rootInputDirectory); @@ -684,18 +671,22 @@ public final class AutoIngestManager extends Observable implements PropertyChang return CaseDeletionResult.FAILED; } - /* - * Acquire an exclusive lock on the case so it can be safely deleted. - * This will fail if the case is open for review or a deletion operation - * on this case is already in progress on another node. - */ CaseDeletionResult result = CaseDeletionResult.FULLY_DELETED; List manifestFileLocks = new ArrayList<>(); - try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString())) { - if (null == caseLock) { - return CaseDeletionResult.FAILED; - } + try { synchronized (jobsLock) { + /* + * Get the case metadata. + */ + CaseMetadata metaData; + Path caseMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), caseName + CaseMetadata.getFileExtension()); + try { + metaData = new CaseMetadata(caseMetaDataFilePath); + } catch (CaseMetadata.CaseMetadataException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Failed to get case metadata file %s for case %s at %s", caseMetaDataFilePath, caseName, caseDirectoryPath), ex); + return CaseDeletionResult.FAILED; + } + /* * Do a fresh input directory scan. */ @@ -703,12 +694,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang scanner.scan(); Set manifestPaths = casesToManifests.get(caseName); if (null == manifestPaths) { - SYS_LOGGER.log(Level.SEVERE, "No manifest paths found for case {0}", caseName); + SYS_LOGGER.log(Level.SEVERE, String.format("No manifest paths found for case %s at %s", caseName, caseDirectoryPath)); return CaseDeletionResult.FAILED; } /* - * Get all of the required manifest locks. + * Get exclusive locks on all of the manifests for the case. + * This will exclude other auot ingest nodes from doing anything + * with the case. */ for (Path manifestPath : manifestPaths) { try { @@ -719,20 +712,18 @@ public final class AutoIngestManager extends Observable implements PropertyChang return CaseDeletionResult.FAILED; } } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to acquire manifest lock for %s for case %s", manifestPath, caseName), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to acquire manifest lock for %s for case %s at %s", manifestPath, caseName, caseDirectoryPath), ex); return CaseDeletionResult.FAILED; } } - /* - * Get the case metadata. - */ - CaseMetadata metaData; - Path caseMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), caseName + CaseMetadata.getFileExtension()); try { - metaData = new CaseMetadata(caseMetaDataFilePath); - } catch (CaseMetadata.CaseMetadataException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Failed to delete case metadata file %s for case %s", caseMetaDataFilePath, caseName)); + /* + * Physically delete the case. + */ + Case.deleteCase(metaData); + } catch (CaseActionException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Failed to physically delete case %s at %s", caseName, caseDirectoryPath), ex); return CaseDeletionResult.FAILED; } @@ -745,56 +736,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang nodeData.setStatus(ManifestNodeData.ProcessingStatus.DELETED); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); } catch (InterruptedException | CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set delete flag on manifest data for %s for case %s", manifestPath, caseName), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set delete flag on manifest data for %s for case %s at %s", manifestPath, caseName, caseDirectoryPath), ex); return CaseDeletionResult.PARTIALLY_DELETED; } } - /* - * Try to unload/delete the Solr core from the Solr server. Do - * this before deleting the case directory because the index - * files are in the case directory and the deletion will fail if - * the core is not unloaded first. - */ - String textIndexName = metaData.getTextIndexName(); - try { - unloadSolrCore(metaData.getTextIndexName()); - } catch (Exception ex) { - /* - * Could be a problem, or it could be that the core was - * already unloaded (e.g., by the server due to resource - * constraints). - */ - SYS_LOGGER.log(Level.WARNING, String.format("Error deleting text index %s for %s", textIndexName, caseName), ex); //NON-NLS - } - - /* - * Delete the case database from the database server. - */ - String caseDatabaseName = metaData.getCaseDatabaseName(); - try { - deleteCaseDatabase(caseDatabaseName); - } catch (SQLException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Unable to delete case database %s for %s", caseDatabaseName, caseName), ex); //NON-NLS - result = CaseDeletionResult.PARTIALLY_DELETED; - } catch (UserPreferencesException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error accessing case database connection info, unable to delete case database %s for %s", caseDatabaseName, caseName), ex); //NON-NLS - result = CaseDeletionResult.PARTIALLY_DELETED; - } catch (ClassNotFoundException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Cannot load database driver, unable to delete case database %s for %s", caseDatabaseName, caseName), ex); //NON-NLS - result = CaseDeletionResult.PARTIALLY_DELETED; - } - - /* - * Delete the case directory. - */ - File caseDirectory = caseDirectoryPath.toFile(); - FileUtil.deleteDir(caseDirectory); - if (caseDirectory.exists()) { - SYS_LOGGER.log(Level.SEVERE, String.format("Failed to delete case directory %s for case %s", caseDirectoryPath, caseName)); - return CaseDeletionResult.PARTIALLY_DELETED; - } - /* * Remove the jobs for the case from the pending jobs queue and * completed jobs list. @@ -809,27 +755,27 @@ public final class AutoIngestManager extends Observable implements PropertyChang notifyObservers(Event.CASE_DELETED); return result; - } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error acquiring coordination service lock on case %s", caseName), ex); - return CaseDeletionResult.FAILED; - } finally { + /* + * Always release the manifest locks, regardless of the outcome. + */ for (Lock lock : manifestFileLocks) { try { lock.release(); } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Failed to release manifest file lock when deleting case %s", caseName), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Failed to release manifest file lock when deleting case %s at %s", caseName, caseDirectoryPath), ex); } } } } - + /** * Get the current snapshot of the job lists. + * * @return Snapshot of jobs lists */ - JobsSnapshot getCurrentJobsSnapshot(){ - synchronized(jobsLock){ + JobsSnapshot getCurrentJobsSnapshot() { + synchronized (jobsLock) { List runningJobs = new ArrayList<>(); getJobs(null, runningJobs, null); return new JobsSnapshot(pendingJobs, runningJobs, completedJobs); @@ -895,9 +841,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang * Starts the process of cancelling the current job. * * Note that the current job is included in the running list for a while - * because it can take some time - * for the automated ingest process for the job to be shut down in - * an orderly fashion. + * because it can take some time for the automated ingest process for the + * job to be shut down in an orderly fashion. */ void cancelCurrentJob() { if (State.RUNNING != state) { @@ -1655,8 +1600,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @throws CoordinationServiceException if there is an error while * acquiring or releasing a * manifest file lock. - * @throws InterruptedException if the thread is interrupted while - * reading the lock data + * @throws InterruptedException if the thread is interrupted + * while reading the lock data */ private Lock dequeueAndLockNextJob() throws CoordinationServiceException, InterruptedException { SYS_LOGGER.log(Level.INFO, "Checking pending jobs queue for ready job, enforcing max jobs per case"); @@ -1694,8 +1639,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @throws CoordinationServiceException if there is an error while * acquiring or releasing a * manifest file lock. - * @throws InterruptedException if the thread is interrupted while - * reading the lock data + * @throws InterruptedException if the thread is interrupted + * while reading the lock data */ private Lock dequeueAndLockNextJob(boolean enforceMaxJobsPerCase) throws CoordinationServiceException, InterruptedException { Lock manifestLock = null; @@ -1714,18 +1659,18 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ continue; } - + ManifestNodeData nodeData = new ManifestNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - if(! nodeData.getStatus().equals(PENDING)){ + if (!nodeData.getStatus().equals(PENDING)) { /* - * Due to a timing issue or a missed event, - * a non-pending job has ended up on the pending queue. - * Skip the job and remove it from the queue. + * Due to a timing issue or a missed event, a + * non-pending job has ended up on the pending queue. + * Skip the job and remove it from the queue. */ iterator.remove(); continue; } - + if (enforceMaxJobsPerCase) { int currentJobsForCase = 0; for (AutoIngestJob runningJob : hostNamesToRunningJobs.values()) { @@ -1806,9 +1751,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (jobProcessingTaskFuture.isCancelled()) { currentJob.cancel(); } - + nodeData = new ManifestNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath)); - if(currentJob.isCompleted() || currentJob.isCancelled()){ + if (currentJob.isCompleted() || currentJob.isCancelled()) { nodeData.setStatus(COMPLETED); Date completedDate = new Date(); currentJob.setCompletedDate(completedDate); @@ -1820,7 +1765,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang } coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath, nodeData.toArray()); - boolean retry = (!currentJob.isCancelled() && !currentJob.isCompleted()); SYS_LOGGER.log(Level.INFO, "Completed processing of {0}, retry = {1}", new Object[]{manifestPath, retry}); if (currentJob.isCancelled()) { @@ -1892,7 +1836,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } finally { try { - caseForJob.closeCase(); + Case.closeCurrentCase(); } catch (CaseActionException ex) { Manifest manifest = currentJob.getManifest(); throw new CaseManagementException(String.format("Error closing case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); @@ -1983,41 +1927,33 @@ public final class AutoIngestManager extends Observable implements PropertyChang String caseName = manifest.getCaseName(); SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); - try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 30, TimeUnit.MINUTES)) { - if (null != caseLock) { - try { - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); - if (null != caseDirectoryPath) { - Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); - Case.open(metadataFilePath.toString()); - } else { - caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); - Case.create(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); - /* - * Sleep a bit before releasing the lock to ensure - * that the new case folder is visible on the - * network. - */ - Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); - } - currentJob.setCaseDirectoryPath(caseDirectoryPath); - Case caseForJob = Case.getCurrentCase(); - SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); - return caseForJob; - - } catch (CaseActionException ex) { - throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); - } catch (IllegalStateException ex) { - /* - * Deal with the unfortunate fact that - * Case.getCurrentCase throws IllegalStateException. - */ - throw new CaseManagementException(String.format("Error getting current case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); - } - + try { + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); + if (null != caseDirectoryPath) { + Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); + Case.openAsCurrentCase(metadataFilePath.toString()); } else { - throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", manifest.getCaseName(), manifest.getFilePath())); + caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); + Case.createAsCurrentCase(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); + /* + * Sleep a bit before releasing the lock to ensure that the + * new case folder is visible on the network. + */ + Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } + currentJob.setCaseDirectoryPath(caseDirectoryPath); + Case caseForJob = Case.getCurrentCase(); + SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); + return caseForJob; + + } catch (CaseActionException ex) { + throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + } catch (IllegalStateException ex) { + /* + * Deal with the unfortunate fact that Case.getCurrentCase + * throws IllegalStateException. + */ + throw new CaseManagementException(String.format("Error getting current case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); } } @@ -2118,7 +2054,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * Sleep to allow ingest event subscribers to do their event * handling. */ - Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); // RJCTODO: Change the setting description to be more generic + Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } if (currentJob.isCancelled() || jobProcessingTaskFuture.isCancelled()) { @@ -2200,7 +2136,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang for (AutoIngestDataSourceProcessor processor : processorCandidates) { try { int confidence = processor.canProcess(dataSource.getPath()); - if(confidence > 0){ + if (confidence > 0) { validDataSourceProcessorsMap.put(processor, confidence); } } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) { @@ -2219,16 +2155,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang SYS_LOGGER.log(Level.WARNING, "Unsupported data source {0} for {1}", new Object[]{dataSource.getPath(), manifestPath}); // NON-NLS return; } - + // Get an ordered list of data source processors to try List validDataSourceProcessors = validDataSourceProcessorsMap.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); + .sorted(Map.Entry.comparingByValue().reversed()) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); synchronized (ingestLock) { // Try each DSP in decreasing order of confidence - for(AutoIngestDataSourceProcessor selectedProcessor:validDataSourceProcessors){ + for (AutoIngestDataSourceProcessor selectedProcessor : validDataSourceProcessors) { jobLogger.logDataSourceProcessorSelected(selectedProcessor.getDataSourceType()); SYS_LOGGER.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{manifestPath, selectedProcessor.getDataSourceType()}); try { @@ -2249,7 +2185,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang SYS_LOGGER.log(Level.SEVERE, "All data source processors failed to process {0}", dataSource.getPath()); jobLogger.logFailedToAddDataSource(); // Throw an exception. It will get caught & handled upstream and will result in AIM auto-pause. - throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Failed to process " + dataSource.getPath() + " with all data source processors"); + throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Failed to process " + dataSource.getPath() + " with all data source processors"); } } finally { currentJob.setDataSourceProcessor(null); @@ -2325,7 +2261,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobLogger.logDataSourceProcessorCancelled(); } } - + /** * Analyzes the data source content returned by the data source * processor using the configured set of data source level and file @@ -2370,7 +2306,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ ingestLock.wait(); IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); - for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { // RJCTODO: Are "child" jobs IngestJobs or DataSourceIngestJobs? + for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { if (!snapshot.isCancelled()) { List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); if (!cancelledModules.isEmpty()) { @@ -2421,7 +2357,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } finally { IngestManager.getInstance().removeIngestJobEventListener(ingestJobEventListener); - currentJob.setIngestJob(null); // RJCTODO: Consider moving AutoIngestJob into AutoIngestManager so that this method can be made private + currentJob.setIngestJob(null); } } @@ -2686,7 +2622,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * remote jobs. The auto ingest job status event is sent only if auto ingest * manager has a currently running auto ingest job. */ - private final class PeriodicJobStatusEventTask implements Runnable { // RJCTODO: Rename to StatusPublishingTask, especially when publishing to the system dashboard + private final class PeriodicJobStatusEventTask implements Runnable { private final long MAX_SECONDS_WITHOUT_UPDATE = JOB_STATUS_EVENT_INTERVAL_SECONDS * MAX_MISSED_JOB_STATUS_UPDATES; @@ -2704,14 +2640,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang notifyObservers(Event.JOB_STATUS_UPDATED); eventPublisher.publishRemotely(new AutoIngestJobStatusEvent(currentJob)); } - - if(AutoIngestUserPreferences.getStatusDatabaseLoggingEnabled()){ + + if (AutoIngestUserPreferences.getStatusDatabaseLoggingEnabled()) { String message; boolean isError = false; - if(getErrorState().equals(ErrorState.NONE)){ - if(currentJob != null){ - message = "Processing " + currentJob.getManifest().getDataSourceFileName() + - " for case " + currentJob.getManifest().getCaseName(); + if (getErrorState().equals(ErrorState.NONE)) { + if (currentJob != null) { + message = "Processing " + currentJob.getManifest().getDataSourceFileName() + + " for case " + currentJob.getManifest().getCaseName(); } else { message = "Paused or waiting for next case"; } @@ -2719,9 +2655,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang message = getErrorState().toString(); isError = true; } - try{ + try { StatusDatabaseLogger.logToStatusDatabase(message, isError); - } catch (SQLException | UserPreferencesException ex){ + } catch (SQLException | UserPreferencesException ex) { SYS_LOGGER.log(Level.WARNING, "Failed to update status database", ex); } } @@ -2778,7 +2714,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang /* * Events published by an auto ingest manager. The events are published * locally to auto ingest manager clients that register as observers and are - * broadcast to other auto ingest nodes. // RJCTODO: Is this true? + * broadcast to other auto ingest nodes. */ enum Event { @@ -2794,31 +2730,31 @@ public final class AutoIngestManager extends Observable implements PropertyChang } /** - * The current auto ingest error state. + * The current auto ingest error state. */ private enum ErrorState { - NONE ("None"), - COORDINATION_SERVICE_ERROR ("Coordination service error"), + NONE("None"), + COORDINATION_SERVICE_ERROR("Coordination service error"), SHARED_CONFIGURATION_DOWNLOAD_ERROR("Shared configuration download error"), - SERVICES_MONITOR_COMMUNICATION_ERROR ("Services monitor communication error"), - DATABASE_SERVER_ERROR ("Database server error"), - KEYWORD_SEARCH_SERVER_ERROR ("Keyword search server error"), - CASE_MANAGEMENT_ERROR ("Case management error"), - ANALYSIS_STARTUP_ERROR ("Analysis startup error"), - FILE_EXPORT_ERROR ("File export error"), - ALERT_FILE_ERROR ("Alert file error"), - JOB_LOGGER_ERROR ("Job logger error"), - DATA_SOURCE_PROCESSOR_ERROR ("Data source processor error"), - UNEXPECTED_EXCEPTION ("Unknown error"); - + SERVICES_MONITOR_COMMUNICATION_ERROR("Services monitor communication error"), + DATABASE_SERVER_ERROR("Database server error"), + KEYWORD_SEARCH_SERVER_ERROR("Keyword search server error"), + CASE_MANAGEMENT_ERROR("Case management error"), + ANALYSIS_STARTUP_ERROR("Analysis startup error"), + FILE_EXPORT_ERROR("File export error"), + ALERT_FILE_ERROR("Alert file error"), + JOB_LOGGER_ERROR("Job logger error"), + DATA_SOURCE_PROCESSOR_ERROR("Data source processor error"), + UNEXPECTED_EXCEPTION("Unknown error"); + private final String desc; - - private ErrorState(String desc){ + + private ErrorState(String desc) { this.desc = desc; } - + @Override - public String toString(){ + public String toString() { return desc; } } @@ -2853,7 +2789,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @return The jobs collection. */ List getPendingJobs() { - return this.pendingJobs; + return Collections.unmodifiableList(this.pendingJobs); } /** @@ -2862,7 +2798,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @return The jobs collection. */ List getRunningJobs() { - return this.runningJobs; + return Collections.unmodifiableList(this.runningJobs); } /** @@ -2871,14 +2807,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @return The jobs collection. */ List getCompletedJobs() { - return this.completedJobs; + return Collections.unmodifiableList(this.completedJobs); } } - /** - * RJCTODO - */ enum CaseDeletionResult { FAILED, PARTIALLY_DELETED, diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 380bbb15a9..0fe33092ca 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -34,9 +34,6 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; -/** - * RJCTODO - */ @Immutable @ServiceProvider(service = ManifestFileParser.class) public final class AutopsyManifestFileParser implements ManifestFileParser { @@ -47,14 +44,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { private static final String DEVICE_ID_XPATH = "/Manifest/Collection/Image/ID/text()"; private static final String DATA_SOURCE_NAME_XPATH = "/Manifest/Collection/Image/Name/text()"; - - /** - * RJCTODO - * - * @param filePath - * - * @return - */ @Override public boolean fileIsManifest(Path filePath) { boolean fileIsManifest = false; @@ -71,15 +60,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { return fileIsManifest; } - /** - * RJCTODO - * - * @param filePath - * - * @return - * - * @throws org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException - */ @Override public Manifest parse(Path filePath) throws ManifestFileParserException { if (!fileIsManifest(filePath)) { @@ -102,17 +82,6 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { } } - /** - * RJCTODO - * - * @param manifestFilePath - * - * @return - * - * @throws ParserConfigurationException - * @throws SAXException - * @throws IOException - */ private Document createManifestDOM(Path manifestFilePath) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index d5fa88092b..cddf2ed860 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -89,13 +89,6 @@ OpenIDE-Module-Long-Description=\ We make no guarantee that the API of this module will not change, so developers should be careful when relying on it. OpenIDE-Module-Name=Experimental OpenIDE-Module-Short-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. -ReviewModeCasePanel.bnRefresh.text=&Refresh -ReviewModeCasePanel.bnOpen.text=&Open -ReviewModeCasePanel.rbGroupLabel.text=Show Last 10: -ReviewModeCasePanel.rbDays.text=Days -ReviewModeCasePanel.rbWeeks.text=Weeks -ReviewModeCasePanel.rbMonths.text=Months -ReviewModeCasePanel.rbAllCases.text=Everything ReviewModeCasePanel.cannotOpenCase=Cannot Open Case ReviewModeCasePanel.casePathNotFound=Case path not found ReviewModeCasePanel.caseIsLocked=Single-user case is locked. @@ -170,12 +163,10 @@ CopyFilesPanel.ConfirmCopyAdd=exists. Do you really want to copy more files to t CopyFilesPanel.ConfirmCopyYes=Copy CopyFilesPanel.ConfirmCopyNo=Do not copy ConfirmationDialog.ConfirmUnlockHeader=Confirm Case Unlock -ReviewModeCasePanel.bnShowLog.text=&Show Log AutoIngestDashboard.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestDashboard.bnPrioritizeCase.text=Prioriti&ze Case AutoIngestDashboard.bnShowCaseLog.toolTipText=Display case log file for selected case AutoIngestDashboard.bnShowCaseLog.text=Show Case &Log -ReviewModeCasePanel.bnShowLog.toolTipText=Display case log file for selected case CopyFilesPanel.bnCancelPendingJob.text=Ca&ncel CopyFilesPanel.tbDestinationCase.text= CopyFilesPanel.cbThrottleNetwork.text=&Throttle Network @@ -298,3 +289,12 @@ AutoIngestDashboard.bnPrioritizeJob.toolTipText=Move this folder to the top of t AutoIngestDashboard.bnReprocessJob.text=Reprocess Job AutoIngestDashboard.bnPrioritizeFolder.label= AutoIngestDashboard.bnPrioritizeJob.actionCommand= +AutoIngestCasePanel.rbDays.text=Days +AutoIngestCasePanel.rbWeeks.text=Weeks +AutoIngestCasePanel.rbMonths.text=Months +AutoIngestCasePanel.rbAllCases.text=Everything +AutoIngestCasePanel.bnRefresh.text=&Refresh +AutoIngestCasePanel.bnOpen.text=&Open +AutoIngestCasePanel.bnShowLog.toolTipText=Display case log file for selected case +AutoIngestCasePanel.bnShowLog.text=&Show Log +AutoIngestCasePanel.rbGroupLabel.text=Show Last 10: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java index d1ee4c3865..34c361b3c6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java @@ -108,7 +108,7 @@ public class CaseImportPanel extends javax.swing.JPanel implements ImportDoneCal if (!UserPreferences.getIsMultiUserModeEnabled()) { tbOops.setText(MULTI_USER_SETTINGS_MUST_BE_ENABLED); return; - } else if (RuntimeProperties.coreComponentsAreActive()) { + } else if (RuntimeProperties.runningWithGUI()) { tbOops.setText(AIM_MUST_BE_ENABLED); return; } else { @@ -674,7 +674,7 @@ public class CaseImportPanel extends javax.swing.JPanel implements ImportDoneCal private void enableStartButton() { if (UserPreferences.getIsMultiUserModeEnabled() && AutoIngestUserPreferences.getJoinAutoModeCluster() - && (! RuntimeProperties.coreComponentsAreActive()) + && (! RuntimeProperties.runningWithGUI()) && !tbCaseSource.getText().isEmpty() && !tbCaseDestination.getText().isEmpty() && canTalkToDb == true diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java index 3acf895a9d..e5d7f1a6a3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java @@ -29,9 +29,6 @@ import java.util.HashMap; import java.util.Map; import javax.annotation.concurrent.Immutable; -/** - * RJCTODO - */ @Immutable public final class Manifest implements Serializable { @@ -43,17 +40,6 @@ public final class Manifest implements Serializable { private final String dataSourcePath; private final Map manifestProperties; - /** - * RJCTODO - * - * @param manifestFilePath - * @param caseName - * @param deviceId - * @param dataSourcePath - * @param manifestProperties - * - * @throws IOException - */ public Manifest(Path manifestFilePath, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) throws IOException { this.filePath = manifestFilePath.toString(); BasicFileAttributes attrs = Files.readAttributes(manifestFilePath, BasicFileAttributes.class); @@ -64,65 +50,30 @@ public final class Manifest implements Serializable { this.manifestProperties = new HashMap<>(manifestProperties); } - /** - * RJCTODO - * - * @return - */ public Path getFilePath() { return Paths.get(this.filePath); } - /** - * RJCTODO - * - * @return - * @throws IOException - */ public Date getDateFileCreated() { return this.dateFileCreated; } - /** - * RJCTODO - * - * @return - */ public String getCaseName() { return caseName; } - /** - * RJCTODO - * - * @return - */ public String getDeviceId() { return deviceId; } - /** - * RJCTODO - * - * @return - */ public Path getDataSourcePath() { return Paths.get(dataSourcePath); } - /** - * RJCTODO - * @return - */ public String getDataSourceFileName() { return Paths.get(dataSourcePath).getFileName().toString(); } - /** - * RJCTODO - * - * @return - */ public Map getManifestProperties() { return new HashMap<>(manifestProperties); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java index 14111b0410..00fb2b9a5f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java @@ -20,17 +20,11 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.nio.file.Path; -/** - * RJCTODO: - */ public interface ManifestFileParser { boolean fileIsManifest(Path filePath); Manifest parse(Path filePath) throws ManifestFileParserException; - /** - * Exception thrown if a manifest file cannot be parsed. RJCTODO - */ public final static class ManifestFileParserException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java index e4e272edd3..498ac38cee 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestNodeData.java @@ -99,9 +99,6 @@ final class ManifestNodeData { * * @return True or false. */ - // RJCTODO: This is confusing, consider changing the API so that the use case is to - // check the length of the node data from the coordination service before - // constructing an instance of this object. That would be much more clear! boolean coordSvcNodeDataWasSet() { return this.coordSvcNodeDataWasSet; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java index 622912f95a..852ab4714f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java @@ -69,7 +69,7 @@ final class PathUtils { * * @return A list of the output case folder paths. */ - static List findCaseFolders(Path folderToSearch) { // RJCTODO: Rename + static List findCaseFolders(Path folderToSearch) { File searchFolder = new File(folderToSearch.toString()); if (!searchFolder.isDirectory()) { return Collections.emptyList(); @@ -135,7 +135,7 @@ final class PathUtils { * * @return A case folder path with a time stamp suffix. */ - static Path createCaseFolderPath(Path caseFoldersPath, String caseName) { // RJCTODO: Rename + static Path createCaseFolderPath(Path caseFoldersPath, String caseName) { String folderName = caseName + "_" + TimeStampUtils.createTimeStamp(); return Paths.get(caseFoldersPath.toString(), folderName); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java deleted file mode 100644 index 2cae50ef1a..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2015 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.experimental.autoingest; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.swing.SwingUtilities; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; -import org.openide.util.actions.CallableSystemAction; -import org.sleuthkit.autopsy.casemodule.AddImageAction; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.casemodule.CaseNewAction; -import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; - -/** - * Handles opening, locking, and unlocking cases in review mode. Instances of - * this class are tightly coupled to the Autopsy "current case" concept and the - * Autopsy UI, and cases must be opened by code executing in the event - * dispatch thread (EDT). Because of the tight coupling to the UI, exception - * messages are deliberately user-friendly. - */ -final class ReviewModeCaseManager { - - /* - * Provides uniform exceptions with user-friendly error messages. - */ - final class ReviewModeCaseManagerException extends Exception { - - private static final long serialVersionUID = 1L; - - private ReviewModeCaseManagerException(String message) { - super(message); - } - - private ReviewModeCaseManagerException(String message, Throwable cause) { - super(message, cause); - } - - } - - private static final Logger logger = Logger.getLogger(ReviewModeCaseManager.class.getName()); - private static ReviewModeCaseManager instance; - private CoordinationService.Lock currentCaseLock; - - /** - * Gets the review mode case manager. - * - * @return The review mode case manager singleton. - */ - synchronized static ReviewModeCaseManager getInstance() { - if (instance == null) { - instance = new ReviewModeCaseManager(); - } - return instance; - } - - /** - * Constructs a review mode case manager to handles opening, locking, and - * unlocking cases in review mode. Instances of this class are tightly - * coupled to the Autopsy "current case" concept and the Autopsy UI, - * and cases must be opened by code executing in the event dispatch thread - * (EDT). Because of the tight coupling to the UI, exception messages are - * deliberately user-friendly. - * - */ - private ReviewModeCaseManager() { - /* - * Disable the new case action because review mode is only for looking - * at cases created by automated ingest. - */ - CallableSystemAction.get(CaseNewAction.class).setEnabled(false); - - /* - * Permanently delete the "Open Recent Cases" item in the "File" menu. - * This is quite drastic, as it also affects Autopsy standalone mode on - * this machine, but review mode is only for looking at cases created by - * automated ingest. - */ - FileObject root = FileUtil.getConfigRoot(); - FileObject openRecentCasesMenu = root.getFileObject("Menu/Case/OpenRecentCase"); - if (openRecentCasesMenu != null) { - try { - openRecentCasesMenu.delete(); - } catch (IOException ex) { - ReviewModeCaseManager.logger.log(Level.WARNING, "Unable to remove Open Recent Cases file menu item", ex); - } - } - } - - /* - * Gets a list of the cases in the top level case folder used by automated - * ingest. - */ - List getCases() { - List cases = new ArrayList<>(); - List caseFolders = PathUtils.findCaseFolders(Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder())); - for (Path caseFolderPath : caseFolders) { - cases.add(new AutoIngestCase(caseFolderPath)); - } - return cases; - } - - /** - * Attempts to open a case as the current case. Assumes it is called by code - * executing in the event dispatch thread (EDT). - * - * @param caseMetadataFilePath Path to the case metadata file. - * - * @throws ReviewModeCaseManagerException - */ - /* - * TODO (RC): With a little work, the lock acquisition/release could be done - * by a thread in a single thread executor, removing the "do it in the EDT" - * requirement - */ - synchronized void openCaseInEDT(Path caseMetadataFilePath) throws ReviewModeCaseManagerException { - try { - /* - * Open the case. - */ - Case.open(caseMetadataFilePath.toString()); - - /** - * Disable the add data source action in review mode. This has to be - * done here because Case.open() calls Case.doCaseChange() and the - * latter method enables the action. Since Case.doCaseChange() - * enables the menus on EDT by calling SwingUtilities.invokeLater(), - * we have to do the same thing here to maintain the order of - * execution. - */ - SwingUtilities.invokeLater(() -> { - CallableSystemAction.get(AddImageAction.class).setEnabled(false); - }); - - } catch (CaseActionException ex) { - throw new ReviewModeCaseManagerException(String.format("Could not open the case (%s), contract administrator", ex.getMessage()), ex); - } - } -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java index fbffcf7ece..ce76b1eea1 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/StartupWindow.java @@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.casemodule.CueBannerPanel; import org.sleuthkit.autopsy.casemodule.StartupWindowInterface; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboard; -import org.sleuthkit.autopsy.experimental.autoingest.ReviewModeCasePanel; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestCasePanel; /** * The default implementation of the Autopsy startup window @@ -47,7 +47,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa private static Dimension DIMENSIONS = new Dimension(750, 400); private static CueBannerPanel welcomeWindow; private static final long serialVersionUID = 1L; - private ReviewModeCasePanel caseManagementPanel = null; + private AutoIngestCasePanel caseManagementPanel = null; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); public StartupWindow() { @@ -120,7 +120,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa break; case REVIEW: this.setTitle(NbBundle.getMessage(StartupWindow.class, "StartupWindow.ReviewMode") + " (" + LOCAL_HOST_NAME + ")"); - caseManagementPanel = new ReviewModeCasePanel(this); + caseManagementPanel = new AutoIngestCasePanel(this); setIconImage(ImageUtilities.loadImage("org/sleuthkit/autopsy/experimental/images/frame.gif", false)); //NON-NLS add(caseManagementPanel); break; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 55fb260bbd..f85b5a4050 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -917,7 +917,7 @@ public final class ImageGalleryController implements Executor { @Override public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.coreComponentsAreActive() == false) { + if (RuntimeProperties.runningWithGUI() == false) { /* * Running in "headless" mode, no need to process any events. * This cannot be done earlier because the switch to core @@ -978,7 +978,7 @@ public final class ImageGalleryController implements Executor { @Override public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.coreComponentsAreActive() == false) { + if (RuntimeProperties.runningWithGUI() == false) { /* * Running in "headless" mode, no need to process any events. * This cannot be done earlier because the switch to core diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index a5024fd7b0..512d62238a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -82,7 +82,7 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { @Override public void focusLost(FocusEvent e) { - if (keywordTextField.getText().equals("")) { + if (keywordTextField.getText().isEmpty()) { clearSearchBox(); } } @@ -124,6 +124,11 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { keywordTextField.setText(""); } + void setRegexSearchEnabled(boolean enabled) { + exactRadioButton.setSelected(true); + regexRadioButton.setEnabled(enabled); + } + /** * Gets a single keyword list consisting of a single keyword encapsulating * the input term(s)/phrase/substring/regex. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index 68c482edd4..2fa8f652e5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2003-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,12 +24,12 @@ import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel that provides a toolbar button for the dropdown keyword list search @@ -39,7 +39,7 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; class DropdownToolbar extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(DropdownToolbar.class.getName()); + private static final Logger logger = Logger.getLogger(DropdownToolbar.class.getName()); private static DropdownToolbar instance; private SearchSettingsChangeListener searchSettingsChangeListener; private boolean active = false; @@ -82,7 +82,7 @@ class DropdownToolbar extends javax.swing.JPanel { listsPanel.addSearchButtonActionListener((ActionEvent e) -> { listsMenu.setVisible(false); }); - + // Adding border of six to account for menu border listsMenu.setSize(listsPanel.getPreferredSize().width + 6, listsPanel.getPreferredSize().height + 6); listsMenu.add(listsPanel); @@ -150,8 +150,60 @@ class DropdownToolbar extends javax.swing.JPanel { } searchMenu.show(searchDropButton, searchDropButton.getWidth() - searchMenu.getWidth(), searchDropButton.getHeight() - 1); } - - + + private class SearchSettingsChangeListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String changed = evt.getPropertyName(); + if (changed.equals(Case.Events.CURRENT_CASE.toString())) { + dropPanel.clearSearchBox(); + if (RuntimeProperties.runningWithGUI() || null == evt.getNewValue()) { + Server server = KeywordSearch.getServer(); + if (server.coreIsOpen()) { + try { + Index indexInfo = server.getIndexInfo(); + listsButton.setEnabled(IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())); + searchDropButton.setEnabled(true); + dropPanel.setRegexSearchEnabled(IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())); + active = true; + } catch (KeywordSearchModuleException ex) { + searchDropButton.setEnabled(false); + listsButton.setEnabled(false); + active = false; + } + } else { + searchDropButton.setEnabled(false); + listsButton.setEnabled(false); + active = false; + } + } else { + searchDropButton.setEnabled(false); + listsButton.setEnabled(false); + active = false; + } + } else if (changed.equals(Server.CORE_EVT)) { + final Server.CORE_EVT_STATES state = (Server.CORE_EVT_STATES) evt.getNewValue(); + switch (state) { + case STARTED: + try { + final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); + KeywordSearch.fireNumIndexedFilesChange(null, numIndexedFiles); + } catch (NoOpenCoreException ex) { + logger.log(Level.SEVERE, "Error executing Solr query, {0}", ex); //NON-NLS + } catch (KeywordSearchModuleException se) { + logger.log(Level.SEVERE, "Error executing Solr query, {0}", se.getMessage()); //NON-NLS + } + break; + case STOPPED: + break; + default: + } + } + } + + } + /** * 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 @@ -248,39 +300,4 @@ class DropdownToolbar extends javax.swing.JPanel { private javax.swing.JPopupMenu searchMenu; // End of variables declaration//GEN-END:variables - private class SearchSettingsChangeListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - String changed = evt.getPropertyName(); - if (changed.equals(Case.Events.CURRENT_CASE.toString())) { - dropPanel.clearSearchBox(); - setFields(null != evt.getNewValue() && RuntimeProperties.coreComponentsAreActive()); - } else if (changed.equals(Server.CORE_EVT)) { - final Server.CORE_EVT_STATES state = (Server.CORE_EVT_STATES) evt.getNewValue(); - switch (state) { - case STARTED: - try { - final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); - KeywordSearch.fireNumIndexedFilesChange(null, numIndexedFiles); - } catch (NoOpenCoreException ex) { - LOGGER.log(Level.SEVERE, "Error executing Solr query, {0}", ex); //NON-NLS - } catch (KeywordSearchModuleException se) { - LOGGER.log(Level.SEVERE, "Error executing Solr query, {0}", se.getMessage()); //NON-NLS - } - break; - case STOPPED: - break; - default: - } - } - } - - private void setFields(boolean enabled) { - searchDropButton.setEnabled(enabled); - listsButton.setEnabled(enabled); - active = enabled; - } - } - } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java index 3e7a7c4ec7..80b686751b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java @@ -31,7 +31,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.math.NumberUtils; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; import org.sleuthkit.autopsy.coreutils.PlatformUtil; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java index ead8609d32..4dabc90715 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexUpgrader.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.logging.Level; import org.apache.commons.lang.math.NumberUtils; import org.openide.modules.InstalledFileLocator; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService; import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; @@ -44,30 +44,37 @@ class IndexUpgrader { JAVA_PATH = PlatformUtil.getJavaPath(); } - void performIndexUpgrade(String newIndexDir, Index indexToUpgrade, String tempResultsDir) throws AutopsyService.AutopsyServiceException { + Index performIndexUpgrade(String newIndexDir, Index indexToUpgrade, String tempResultsDir) throws AutopsyService.AutopsyServiceException { // ELTODO Check for cancellation at whatever points are feasible // Run the upgrade tools on the contents (core) in ModuleOutput/keywordsearch/data/solrX_schema_Y/index File tmpDir = Paths.get(tempResultsDir, "IndexUpgrade").toFile(); //NON-NLS tmpDir.mkdirs(); + Index upgradedIndex; double currentSolrVersion = NumberUtils.toDouble(indexToUpgrade.getSolrVersion()); try { // upgrade from Solr 4 to 5 currentSolrVersion = upgradeSolrIndexVersion4to5(currentSolrVersion, newIndexDir, tempResultsDir); // upgrade from Solr 5 to 6 currentSolrVersion = upgradeSolrIndexVersion5to6(currentSolrVersion, newIndexDir, tempResultsDir); + + // create upgraded index object + upgradedIndex = new Index(newIndexDir, Double.toString(currentSolrVersion), indexToUpgrade.getSchemaVersion()); + upgradedIndex.setNewIndex(true); } catch (Exception ex) { // catch-all firewall for exceptions thrown by Solr upgrade tools throw new AutopsyService.AutopsyServiceException("Exception while running Solr index upgrade in " + newIndexDir, ex); //NON-NLS } finally { if (currentSolrVersion != NumberUtils.toDouble(IndexFinder.getCurrentSolrVersion())) { // upgrade did not complete, delete the new index directories + upgradedIndex = null; if (!new File(newIndexDir).delete()) { logger.log(Level.SEVERE, "Unable to delete folder {0}", newIndexDir); //NON-NLS } } } + return upgradedIndex; } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 4a0c3902c8..d5d0afad1e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @@ -114,6 +115,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { /** * Records the ingest status for a given file for a given ingest job. Used * for final statistics at the end of the job. + * * @param ingestJobId id of ingest job * @param fileId id of file * @param status ingest status of the file @@ -140,6 +142,10 @@ public final class KeywordSearchIngestModule implements FileIngestModule { * retrieves settings, keyword lists to run on * */ + @Messages({ + "KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get schema version for text index.", + "# {0} - schema version number", "KeywordSearchIngestModule.startupMessage.indexSchemaNotSupported=Adding text no longer supported for schema version {0} of the text index." + }) @Override public void startUp(IngestJobContext context) throws IngestModuleException { initialized = false; @@ -151,11 +157,21 @@ public final class KeywordSearchIngestModule implements FileIngestModule { throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startUp_noOpenCore_msg()); } + try { + Index indexInfo = server.getIndexInfo(); + if (!IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())) { + throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startupMessage_indexSchemaNotSupported(indexInfo.getSchemaVersion())); + } + } catch (KeywordSearchModuleException ex) { + throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startupMessage_failedToGetIndexSchema(), ex); + } + try { fileTypeDetector = new FileTypeDetector(); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } + ingester = Ingester.getDefault(); this.context = context; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 28859549d3..9e99394a34 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -23,8 +23,10 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.logging.Level; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.response.QueryResponse; @@ -221,11 +223,19 @@ class LuceneQuery implements KeywordSearchQuery { * will get picked up in the next one. */ final String docId = resultDoc.getFieldValue(Server.Schema.ID.toString()).toString(); final Integer chunkSize = (Integer) resultDoc.getFieldValue(Server.Schema.CHUNK_SIZE.toString()); - final String content_str = resultDoc.get(Server.Schema.CONTENT_STR.toString()).toString(); + String content_str = Objects.toString(resultDoc.get(Server.Schema.CONTENT_STR.toString()), null); - Integer firstOccurence = content_str.indexOf(strippedQueryString); - if (firstOccurence < chunkSize) { + double indexSchemaVersion = NumberUtils.toDouble(KeywordSearch.getServer().getIndexInfo().getSchemaVersion()); + if (indexSchemaVersion < 2.0) { + //old schema versions don't support chunk_size or the content_str fields, so just accept hits matches.add(createKeywordtHit(highlightResponse, docId)); + } else { + //for new schemas, check that the hit is before the chunk/window boundary. + int firstOccurence = StringUtils.indexOf(content_str, strippedQueryString); + //there is no chunksize field for "parent" entries in the index + if (chunkSize != null && firstOccurence < chunkSize) { + matches.add(createKeywordtHit(highlightResponse, docId)); + } } } catch (TskException ex) { return matches; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index a440780a3f..2a8f828a40 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -263,7 +263,8 @@ final class RegexQuery implements KeywordSearchQuery { while (hitMatcher.find(offset)) { StringBuilder snippet = new StringBuilder(); - if (hitMatcher.start() >= chunkSize) { + //"parent" entries in the index don't have chunk size, so just accept those hits + if (chunkSize != null && hitMatcher.start() >= chunkSize) { break; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 79c91b82ab..10f1c0f72d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -696,6 +696,19 @@ public class Server { } } + Index getIndexInfo() throws KeywordSearchModuleException { + currentCoreLock.readLock().lock(); + try { + if (null != currentCore) { + return currentCore.getIndexInfo(); + } else { + throw new KeywordSearchModuleException("Cannot get text index info, no core is open"); + } + } finally { + currentCoreLock.readLock().unlock(); + } + } + void closeCore() throws KeywordSearchModuleException { currentCoreLock.writeLock().lock(); try { @@ -1320,6 +1333,10 @@ public class Server { return name; } + private Index getIndexInfo() { + return this.textIndex; + } + private QueryResponse query(SolrQuery sq) throws SolrServerException, IOException { return solrCore.query(sq); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 96c2f86ac2..2f49e3295a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -30,7 +30,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; import org.sleuthkit.autopsy.core.RuntimeProperties; -import org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService; +import org.sleuthkit.autopsy.framework.AutopsyService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; @@ -42,11 +42,11 @@ import org.sleuthkit.datamodel.TskCoreException; * An implementation of the KeywordSearchService interface that uses Solr for * text indexing and search. */ -@ServiceProviders(value={ - @ServiceProvider(service=KeywordSearchService.class), - @ServiceProvider(service=AutopsyService.class)} +@ServiceProviders(value = { + @ServiceProvider(service = KeywordSearchService.class), + @ServiceProvider(service = AutopsyService.class)} ) -public class SolrSearchService implements KeywordSearchService, AutopsyService { +public class SolrSearchService implements KeywordSearchService, AutopsyService { private static final Logger logger = Logger.getLogger(IndexFinder.class.getName()); private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS @@ -55,6 +55,14 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService ArtifactTextExtractor extractor = new ArtifactTextExtractor(); + /** + * Adds an artifact to the keyword search text index as a concantenation of + * all of its attributes. + * + * @param artifact The artifact to index. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ @Override public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException { if (artifact == null) { @@ -77,21 +85,12 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } /** - * Checks if we can communicate with Solr using the passed-in host and port. - * Closes the connection upon exit. Throws if it cannot communicate with - * Solr. + * Tries to connect to the keyword search service. * - * When issues occur, it attempts to diagnose them by looking at the - * exception messages, returning the appropriate user-facing text for the - * exception received. This method expects the Exceptions messages to be in - * English and compares against English text. - * - * @param host the remote hostname or IP address of the Solr server - * @param port the remote port for Solr - * - * @throws - * org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException + * @param host The hostname or IP address of the service. + * @param port The port used by the service. * + * @throws KeywordSearchServiceException if cannot connect. */ @Override public void tryConnect(String host, int port) throws KeywordSearchServiceException { @@ -138,11 +137,29 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } } + /** + * Deletes the keyword search text index for a case. + * + * @param textIndexName The text index name. + */ + @Override + public void deleteTextIndex(String textIndexName) { + /* + * Send a core unload request to the Solr server, with the parameters + * that request deleting the index and the instance directory + * (deleteInstanceDir removes everything related to the core, the index + * directory, the configuration files, etc.) set to true. + */ +// String url = "http://" + UserPreferences.getIndexingServerHost() + ":" + UserPreferences.getIndexingServerPort() + "/solr"; +// HttpSolrServer solrServer = new HttpSolrServer(url); +// org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(textIndexName, true, true, solrServer); + } + @Override public void close() throws IOException { } - - /** + + /** * * @param context * @@ -154,11 +171,11 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService /* * Autopsy service providers may not have case-level resources. */ - + // do a case subdirectory search to check for the existence and upgrade status of KWS indexes IndexFinder indexFinder = new IndexFinder(); List indexes = indexFinder.findAllIndexDirs(context.getCase()); - + // check if index needs upgrade Index currentVersionIndex; if (indexes.isEmpty()) { @@ -181,10 +198,9 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService if (indexSolrVersion > currentSolrVersion) { // oops! throw new AutopsyServiceException("Unable to find index to use for Case open"); - } - else if (indexSolrVersion == currentSolrVersion) { + } else if (indexSolrVersion == currentSolrVersion) { // latest Solr version but not latest schema. index should be used in read-only mode and not be upgraded. - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { // pop up a message box to indicate the read-only restrictions. if (!KeywordSearchUtil.displayConfirmDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.title"), NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.msg"), @@ -195,10 +211,9 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } // proceed with case open currentVersionIndex = indexToUpgrade; - } - else { + } else { // index needs to be upgraded to latest supported version of Solr - if (RuntimeProperties.coreComponentsAreActive()) { + if (RuntimeProperties.runningWithGUI()) { //pop up a message box to indicate the restrictions on adding additional //text and performing regex searches and give the user the option to decline the upgrade if (!KeywordSearchUtil.displayConfirmDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexUpgradeDialog.title"), @@ -210,38 +225,31 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService } // ELTODO Check for cancellation at whatever points are feasible - // Copy the existing index and config set into ModuleOutput/keywordsearch/data/solrX_schema_Y/ String newIndexDir = indexFinder.copyIndexAndConfigSet(context.getCase(), indexToUpgrade); // upgrade the existing index to the latest supported Solr version IndexUpgrader indexUpgrader = new IndexUpgrader(); - indexUpgrader.performIndexUpgrade(newIndexDir, indexToUpgrade, context.getCase().getTempDirectory()); - - // set the upgraded index as the index to be used for this case - currentVersionIndex = new Index(newIndexDir, IndexFinder.getCurrentSolrVersion(), indexToUpgrade.getSchemaVersion()); - currentVersionIndex.setNewIndex(true); + currentVersionIndex = indexUpgrader.performIndexUpgrade(newIndexDir, indexToUpgrade, context.getCase().getTempDirectory()); + if (currentVersionIndex == null) { + throw new AutopsyServiceException("Unable to upgrade index to the latest version of Solr"); + } } } } - + // open core try { KeywordSearch.getServer().openCoreForCase(context.getCase(), currentVersionIndex); } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Failed to open or create core for %s", context.getCase().getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { - MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.openCore.notification.msg"), ex.getMessage()); - } + throw new AutopsyServiceException(String.format("Failed to open or create core for %s", context.getCase().getCaseDirectory()), ex); } } /** * * @param context - * - * @throws - * org.sleuthkit.autopsy.corecomponentinterfaces.AutopsyService.AutopsyServiceException + * @throws org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException */ @Override public void closeCaseResources(CaseContext context) throws AutopsyServiceException { @@ -251,18 +259,15 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService try { KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters(); /* - * TODO (AUT-2084): The following code - * KeywordSearch.CaseChangeListener gambles that any - * BlackboardResultWriters (SwingWorkers) will complete - * in less than roughly two seconds - */ + * TODO (AUT-2084): The following code + * KeywordSearch.CaseChangeListener gambles that any + * BlackboardResultWriters (SwingWorkers) will complete in less than + * roughly two seconds + */ Thread.sleep(2000); KeywordSearch.getServer().closeCore(); } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex); //NON-NLS - if (RuntimeProperties.coreComponentsAreActive()) { - MessageNotifyUtil.Notify.error(NbBundle.getMessage(KeywordSearch.class, "KeywordSearch.closeCore.notification.msg"), ex.getMessage()); - } + throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex); } }