From ac4637bf8c0425ee6b4760eda1ed064e840a8421 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 15 Jan 2019 12:47:42 -0500 Subject: [PATCH 1/9] Interim check in of files for improved multiuser case dialog --- .../autopsy/casemodule/Bundle.properties | 9 +- .../sleuthkit/autopsy/casemodule/Case.java | 43 ++- .../autopsy/casemodule/CaseBrowser.java | 265 ------------------ .../autopsy/casemodule/CaseMetadata.java | 15 +- .../autopsy/casemodule/CaseOpenAction.java | 2 +- .../autopsy/casemodule/MultiUserCaseNode.java | 198 +++++++++++++ ...r.form => MultiUserCasesBrowserPanel.form} | 0 .../MultiUserCasesBrowserPanel.java | 259 +++++++++++++++++ .../casemodule/MultiUserCasesDialog.java | 90 ------ .../casemodule/MultiUserCasesRootNode.java | 69 +++++ .../autopsy/casemodule/MultiUserNode.java | 232 --------------- .../casemodule/OpenMultiUserCaseDialog.java | 96 +++++++ ...Panel.form => OpenMultiUserCasePanel.form} | 33 +-- ...Panel.java => OpenMultiUserCasePanel.java} | 128 ++------- .../coordinationservice/CaseNodeData.java | 249 +++++++++++++--- .../InvalidNodeDataException.java | 35 +++ .../coordinationservice/NodeDataUtils.java | 64 +++++ 17 files changed, 1016 insertions(+), 771 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java rename Core/src/org/sleuthkit/autopsy/casemodule/{CaseBrowser.form => MultiUserCasesBrowserPanel.form} (100%) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java rename Core/src/org/sleuthkit/autopsy/casemodule/{MultiUserCasesPanel.form => OpenMultiUserCasePanel.form} (71%) rename Core/src/org/sleuthkit/autopsy/casemodule/{MultiUserCasesPanel.java => OpenMultiUserCasePanel.java} (59%) create mode 100755 Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java create mode 100755 Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 32e02fdbca..d6b8aeb280 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -193,14 +193,10 @@ ReviewModeCasePanel.MetadataFileHeaderText=Metadata File OpenMultiUserCasePanel.jLabel1.text=Recent Cases OpenMultiUserCasePanel.openButton.text=Open OpenMultiUserCasePanel.cancelButton.text=Cancel -MultiUserCasesPanel.bnOpen.text=&Open CueBannerPanel.newCaseLabel.text=New Case CueBannerPanel.openCaseButton.text= CueBannerPanel.openCaseLabel.text=Open Case -MultiUserCasesPanel.bnOpenSingleUserCase.text=Open Single-User Case... CueBannerPanel.newCaseButton.text= -MultiUserCasesPanel.searchLabel.text=Select any case and start typing to search by case name -MultiUserCasesPanel.cancelButton.text=Cancel ImageFilePanel.sectorSizeLabel.text=Sector size: LocalDiskPanel.sectorSizeLabel.text=Sector Size: LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default @@ -240,4 +236,7 @@ ImageFilePanel.sha1HashTextField.text= ImageFilePanel.md5HashTextField.text= ImageFilePanel.errorLabel.text=Error Label ImageFilePanel.hashValuesNoteLabel.text=NOTE: These values will not be validated when the data source is added. -ImageFilePanel.hashValuesLabel.text=Hash Values (optional): \ No newline at end of file +ImageFilePanel.hashValuesLabel.text=Hash Values (optional): +OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name + +OpenMultiUserCasePanel.bnOpenSingleUserCase.text=Open Single-User Case... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 46e9dad2bc..44f1a893af 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -32,6 +32,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; @@ -57,6 +58,7 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; 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; @@ -80,6 +82,7 @@ import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchAction; import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; @@ -1956,9 +1959,9 @@ public class Case { } if (isNewCase) { - createCaseData(progressIndicator); + createCaseData(progressIndicator); // RJCTODO: This name is vague } else { - openCaseData(progressIndicator); + openCaseData(progressIndicator); // RJCTODO: This name is vague } if (Thread.currentThread().isInterrupted()) { @@ -1970,6 +1973,7 @@ public class Case { if (Thread.currentThread().isInterrupted()) { throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); } + } catch (CaseActionException ex) { /* * Cancellation or failure. Clean up. The sleep is a little hack to @@ -2003,6 +2007,19 @@ public class Case { "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file." }) private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException { + // RJCTODO: progress + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(metadata); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | ParseException ex) { + // RJCTODO + } + + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + /* * Create the case directory, if it does not already exist. * @@ -2014,6 +2031,10 @@ public class Case { Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType()); } + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + /* * Create the case database. */ @@ -2065,6 +2086,24 @@ public class Case { }) private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException { try { + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + + // RJCTODO: progress + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setLastAccessDate(new Date()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | CaseNodeData.InvalidDataException ex) { + // RJCTODO + } + + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); String databaseName = metadata.getCaseDatabaseName(); if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java deleted file mode 100644 index 402b64d78f..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017-2018 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.lang.reflect.InvocationTargetException; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.TableColumnModel; -import org.netbeans.swing.etable.ETableColumn; -import org.netbeans.swing.etable.ETableColumnModel; -import org.netbeans.swing.outline.DefaultOutlineModel; -import org.netbeans.swing.outline.Outline; -import org.openide.nodes.Node; -import java.awt.EventQueue; -import java.io.File; -import java.io.IOException; -import java.nio.file.LinkOption; -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.SwingWorker; -import org.openide.explorer.ExplorerManager; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.EmptyNode; - -/** - * A Swing JPanel with a scroll pane child component. The scroll pane contain - * the table of cases. - * - * Used to display a list of multi user cases and allow the user to open one of - * them. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider { - - private static final long serialVersionUID = 1L; - private final Outline outline; - private ExplorerManager em; - private final org.openide.explorer.view.OutlineView outlineView; - private int originalPathColumnIndex = 0; - private static final Logger LOGGER = Logger.getLogger(CaseBrowser.class.getName()); - private LoadCaseListWorker tableWorker; - - @Override - public ExplorerManager getExplorerManager() { - return em; - } - - /** - * Creates a new CaseBrowser - */ - CaseBrowser() { - outlineView = new org.openide.explorer.view.OutlineView(); - initComponents(); - - outline = outlineView.getOutline(); - outlineView.setPropertyColumns( - Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), - Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath()); - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); - customize(); - - } - - /** - * Configures the the table of cases and its columns. - */ - private void customize() { - outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - TableColumnModel columnModel = outline.getColumnModel(); - int dateColumnIndex = 0; - for (int index = 0; index < columnModel.getColumnCount(); index++) { - //get indexes for created date column and path column - if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_metadataFilePath())) { - originalPathColumnIndex = index; - } else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_createdTime())) { - dateColumnIndex = index; - } - } - //Hide path column by default (user can unhide it) - ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex); - ((ETableColumnModel) columnModel).setColumnHidden(column, true); - outline.setRootVisible(false); - - //Sort on Created date column in descending order by default - outline.setColumnSorted(dateColumnIndex, false, 1); - if (null == em) { - em = new ExplorerManager(); - } - caseTableScrollPane.setViewportView(outlineView); - this.setVisible(true); - outline.setRowSelectionAllowed(false); - } - - /** - * Add a listener to changes in case selections in the table - * - * @param listener the ListSelectionListener to add - */ - void addListSelectionListener(ListSelectionListener listener) { - outline.getSelectionModel().addListSelectionListener(listener); - } - - /** - * Get the path to the .aut file for the selected case. - * - * @return the full path to the selected case's .aut file - */ - String getCasePath() { - int[] selectedRows = outline.getSelectedRows(); - if (selectedRows.length == 1) { - try { - return ((Node.Property) outline.getModel().getValueAt(outline.convertRowIndexToModel(selectedRows[0]), originalPathColumnIndex)).getValue().toString(); - } catch (IllegalAccessException | InvocationTargetException ex) { - LOGGER.log(Level.SEVERE, "Unable to get case path from table.", ex); - } - } - return null; - } - - /** - * Check if a row could be and is selected. - * - * @return true if a row is selected, false if no row is selected - */ - boolean isRowSelected() { - return outline.getRowSelectionAllowed() && outline.getSelectedRows().length > 0; - } - - @NbBundle.Messages({"CaseBrowser.caseListLoading.message=Please Wait..."}) - /** - * Gets the list of cases known to the review mode cases manager and - * refreshes the cases table. - */ - void refresh() { - if (tableWorker == null || tableWorker.isDone()) { - outline.setRowSelectionAllowed(false); - //create a new TableWorker to and execute it in a background thread if one is not currently working - //set the table to display text informing the user that the list is being retreived and disable case selection - EmptyNode emptyNode = new EmptyNode(Bundle.CaseBrowser_caseListLoading_message()); - em.setRootContext(emptyNode); - tableWorker = new LoadCaseListWorker(); - tableWorker.execute(); - } - - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - caseTableScrollPane = new javax.swing.JScrollPane(); - - setMinimumSize(new java.awt.Dimension(0, 5)); - setPreferredSize(new java.awt.Dimension(5, 5)); - setLayout(new java.awt.BorderLayout()); - - caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5)); - caseTableScrollPane.setOpaque(false); - caseTableScrollPane.setPreferredSize(new java.awt.Dimension(5, 5)); - add(caseTableScrollPane, java.awt.BorderLayout.CENTER); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane caseTableScrollPane; - // End of variables declaration//GEN-END:variables - - /** - * Swingworker to fetch the updated List of cases in a background thread - */ - private class LoadCaseListWorker extends SwingWorker { - - private List cases; - - /** - * Gets a list of the cases in the top level case folder - * - * @return List of cases. - * - * @throws CoordinationServiceException - */ - private List getCases() throws CoordinationService.CoordinationServiceException { - List caseList = new ArrayList<>(); - List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); - - for (String node : nodeList) { - Path casePath; - try { - casePath = Paths.get(node).toRealPath(LinkOption.NOFOLLOW_LINKS); - - File caseFolder = casePath.toFile(); - if (caseFolder.exists()) { - /* - * Search for '*.aut' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - continue; - } - String autFilePath = null; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(".aut")) { - try { - caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); - } catch (CaseMetadata.CaseMetadataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } - break; - } - } - } - } catch (IOException ignore) { - //if a path could not be resolved to a real path do add it to the caseList - } - } - return caseList; - } - - @Override - protected Void doInBackground() throws Exception { - - try { - cases = getCases(); - } catch (CoordinationService.CoordinationServiceException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while refreshing the table.", ex); //NON-NLS - } - return null; - } - - @Override - protected void done() { - EventQueue.invokeLater(() -> { - MultiUserNode caseListNode = new MultiUserNode(cases); - em.setRootContext(caseListNode); - outline.setRowSelectionAllowed(true); - }); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 6e47363db5..91c1ca216c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -52,7 +52,8 @@ import org.xml.sax.SAXException; 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 DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss (z)"; + private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING); /* * Fields from schema version 1 @@ -92,6 +93,7 @@ public final class CaseMetadata { private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS + /* * Unread fields, regenerated on save. */ @@ -119,6 +121,15 @@ public final class CaseMetadata { return FILE_EXTENSION; } + /** + * Gets the date format used for dates in case metadata. + * + * @return The date format. + */ + public static DateFormat getDateFormat() { + return new SimpleDateFormat(DATE_FORMAT_STRING); + } + /** * Constructs a CaseMetadata object for a new case. The metadata is not * persisted to the case metadata file until writeFile or a setX method is @@ -295,7 +306,7 @@ public final class CaseMetadata { * * @return The date this case was created, as a string. */ - String getCreatedDate() { + public String getCreatedDate() { return createdDate; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index a8e18da650..47c002d36b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -161,7 +161,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (multiUserCaseWindow == null) { - multiUserCaseWindow = MultiUserCasesDialog.getInstance(); + multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance(); } multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); multiUserCaseWindow.setVisible(true); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java new file mode 100755 index 0000000000..0a762f5f3b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -0,0 +1,198 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Desktop; +import java.awt.event.ActionEvent; +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.AbstractAction; +import javax.swing.Action; +import javax.swing.SwingUtilities; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * A NetBeans Explorer View node that represents a multi-user case. + */ +final class MultiUserCaseNode extends AbstractNode { + + private static final Logger logger = Logger.getLogger(MultiUserCaseNode.class.getName()); + private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; + private final CaseNodeData caseNodeData; + private final Path caseMetadataFilePath; + private final Path caseAutoIngestLogFilePath; + + /** + * Constructs a NetBeans Explorer View node that represents a multi-user + * case. + * + * @param caseNodeData The coordination service node data for the case. + */ + MultiUserCaseNode(CaseNodeData caseNodeData) { + super(Children.LEAF); + super.setName(caseNodeData.getDisplayName()); + setName(caseNodeData.getDisplayName()); + setDisplayName(caseNodeData.getDisplayName()); + this.caseNodeData = caseNodeData; + this.caseMetadataFilePath = Paths.get(caseNodeData.getDirectory().toString(), caseNodeData.getName() + CaseMetadata.getFileExtension()); + this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); + } + + @NbBundle.Messages({ + "CaseNode.column.name=Name", + "CaseNode.column.createTime=Create Time", + "CaseNode.column.path=Path" + }) + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(), + Bundle.CaseNode_column_name(), + Bundle.CaseNode_column_name(), + caseNodeData.getDisplayName())); + sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createTime(), + Bundle.CaseNode_column_createTime(), + Bundle.CaseNode_column_createTime(), + caseNodeData.getCreateDate())); + sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_path(), + Bundle.CaseNode_column_path(), + Bundle.CaseNode_column_path(), + caseNodeData.getDirectory().toString())); + return sheet; + } + + @Override + public Action[] getActions(boolean context) { + List actions = new ArrayList<>(); + actions.add(new OpenMultiUserCaseAction(this.caseMetadataFilePath)); + actions.add(new OpenCaseAutoIngestLogAction(this.caseAutoIngestLogFilePath)); + return actions.toArray(new Action[actions.size()]); + } + + @Override + public Action getPreferredAction() { + return new OpenMultiUserCaseAction(this.caseMetadataFilePath); + } + + /** + * An action that opens the specified case and hides the multi-user case + * panel. + */ + @NbBundle.Messages({ + "MultiUserNode.OpenMultiUserCaseAction.menuItemText=Open Case", + "# {0} - caseErrorMessage", "MultiUserNode.OpenMultiUserCaseAction.caseOpeningErrorErrorMsg=Failed to open case: {0}" + }) + private static final class OpenMultiUserCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private final Path caseMetadataFilePath; + + OpenMultiUserCaseAction(Path caseMetadataFilePath) { + super(Bundle.MultiUserNode_OpenMultiUserCaseAction_menuItemText()); + this.caseMetadataFilePath = caseMetadataFilePath; + } + + public void actionPerformed(ActionEvent e) { + StartupWindowProvider.getInstance().close(); + OpenMultiUserCaseDialog.getInstance().setVisible(false); + new Thread(() -> { + try { + Case.openAsCurrentCase(caseMetadataFilePath.toString()); + } catch (CaseActionException ex) { + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS + /* + * CaseActionException error messages are user-friendly. + */ + MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage())); + } + SwingUtilities.invokeLater(() -> { + StartupWindowProvider.getInstance().open(); + OpenMultiUserCaseDialog.getInstance().setVisible(true); + }); + } + }).start(); + } + + @Override + public Object clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + + } + + /** + * An action that opens the case auto ingest log for the specified case. + */ + @NbBundle.Messages({ + "MultiUserNode.OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File", + "MultiUserNode.OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.", + "MultiUserNode.OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.", + }) + private static final class OpenCaseAutoIngestLogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private final Path caseAutoIngestLogFilePath; + + OpenCaseAutoIngestLogAction(Path caseAutoIngestLogFilePath) { + super(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_menuItemText()); + this.caseAutoIngestLogFilePath = caseAutoIngestLogFilePath; + this.setEnabled(caseAutoIngestLogFilePath.toFile().exists()); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + if (caseAutoIngestLogFilePath.toFile().exists()) { + Desktop.getDesktop().edit(caseAutoIngestLogFilePath.toFile()); + } else { + MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_deletedLogErrorMsg()); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error opening case auto ingest log file at %s", caseAutoIngestLogFilePath), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_logOpenFailedErrorMsg()); + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.form similarity index 100% rename from Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form rename to Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.form diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java new file mode 100644 index 0000000000..2a99346792 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java @@ -0,0 +1,259 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 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 javax.swing.ListSelectionModel; +import javax.swing.table.TableColumnModel; +import org.netbeans.swing.etable.ETableColumn; +import org.netbeans.swing.etable.ETableColumnModel; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import java.awt.EventQueue; +import java.io.File; +import java.io.IOException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.openide.explorer.ExplorerManager; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.EmptyNode; +import java.util.concurrent.ExecutionException; +import org.openide.explorer.view.OutlineView; + +/** + * A JPanel with a scroll pane child component that contains a NetBeans + * OutlineView that can be used to display a list of the multi-user cases known + * to the coordination service. + */ +@SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives +class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(MultiUserCasesBrowserPanel.class.getName()); + private ExplorerManager explorerManager; // RJCTODO: COnsider making this final + private final Outline outline; + private final OutlineView outlineView; + private LoadCaseListWorker loadCaseListWorker; + + /** + * Constructs a JPanel with a scroll pane child component that contains a + * NetBeans OutlineView that can be used to display a list of the multi-user + * cases known to the coordination service. + */ + MultiUserCasesBrowserPanel() { + outlineView = new org.openide.explorer.view.OutlineView(); + initComponents(); + outline = outlineView.getOutline(); + customizeOutlineView(); + } + + /** + * Configures the the table of cases and its columns. + */ + private void customizeOutlineView() { + outlineView.setPropertyColumns( + Bundle.CaseNode_column_createTime(), Bundle.CaseNode_column_createTime(), // RJCTODO: Move these into this file? + Bundle.CaseNode_column_path(), Bundle.CaseNode_column_path()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + TableColumnModel columnModel = outline.getColumnModel(); + int pathColumnIndex = 0; + int dateColumnIndex = 0; + for (int index = 0; index < columnModel.getColumnCount(); index++) { + if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_path())) { + pathColumnIndex = index; + } else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_createTime())) { + dateColumnIndex = index; + } + } + + /* + * Hide path column by default (user can unhide it) + */ + ETableColumn column = (ETableColumn) columnModel.getColumn(pathColumnIndex); + ((ETableColumnModel) columnModel).setColumnHidden(column, true); + outline.setRootVisible(false); + + /* + * Sort on Created date column in descending order by default. + */ + outline.setColumnSorted(dateColumnIndex, false, 1); + + if (null == explorerManager) { + explorerManager = new ExplorerManager(); + } + + caseTableScrollPane.setViewportView(outlineView); + this.setVisible(true); + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + /** + * Gets the list of cases known to the review mode cases manager and + * refreshes the cases table. + */ + @NbBundle.Messages({ + "MultiUserCasesBrowserPanel.waitNode.message=Please Wait..." + }) + void refreshCases() { + if (loadCaseListWorker == null || loadCaseListWorker.isDone()) { + /* + * RJCTODO: Explain this or move the data fetching into the create + * keys method of the nodes... + */ + EmptyNode emptyNode = new EmptyNode(Bundle.MultiUserCasesBrowserPanel_waitNode_message()); + explorerManager.setRootContext(emptyNode); + + loadCaseListWorker = new LoadCaseListWorker(); + loadCaseListWorker.execute(); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + caseTableScrollPane = new javax.swing.JScrollPane(); + + setMinimumSize(new java.awt.Dimension(0, 5)); + setPreferredSize(new java.awt.Dimension(5, 5)); + setLayout(new java.awt.BorderLayout()); + + caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5)); + caseTableScrollPane.setOpaque(false); + caseTableScrollPane.setPreferredSize(new java.awt.Dimension(5, 5)); + add(caseTableScrollPane, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane caseTableScrollPane; + // End of variables declaration//GEN-END:variables + + /** + * A background task that gets the multi-user case data from the case + * directory lock coordination service nodes. + */ + private class LoadCaseListWorker extends SwingWorker, Void> { + + private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS + private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS + + @Override + protected List doInBackground() throws Exception { + final List cases = new ArrayList<>(); + final CoordinationService coordinationService = CoordinationService.getInstance(); + final List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); + for (String nodeName : nodeList) { + /* + * Ignore case name lock nodes. + */ + final Path nodeNameAsPath = Paths.get(nodeName); + if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) { + continue; + } + + /* + * Ignore case auto ingest log lock nodes and resource lock + * nodes. + */ + final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString(); + if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME) || lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) { + continue; + } + + /* + * Get the data from the case directory lock node. This data may not exist + * for "legacy" nodes. If it is missing, create it. + */ + try { + CaseNodeData nodeData; + byte[] nodeBytes = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, nodeName); + if (nodeBytes != null && nodeBytes.length > 0) { + nodeData = new CaseNodeData(nodeBytes); + if (nodeData.getVersion() > 0) { + cases.add(nodeData); + } else { + nodeData = createNodeDataFromCaseMetadata(nodeName); + } + } else { + nodeData = createNodeDataFromCaseMetadata(nodeName); + } + cases.add(nodeData); + + } catch (CoordinationService.CoordinationServiceException | CaseNodeData.InvalidDataException | IOException | CaseMetadata.CaseMetadataException ex) { + logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); + } + + } + return cases; + } + + @Override + protected void done() { + try { + final List cases = get(); + EventQueue.invokeLater(() -> { + MultiUserCasesRootNode caseListNode = new MultiUserCasesRootNode(cases); + explorerManager.setRootContext(caseListNode); + }); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Unexpected interrupt during background processing", ex); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Error during background processing", ex); + } + } + + private CaseNodeData createNodeDataFromCaseMetadata(String nodeName) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException { + Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS); + File caseDirectory = caseDirectoryPath.toFile(); + if (caseDirectory.exists()) { + File[] files = caseDirectory.listFiles(); + for (File file : files) { + String name = file.getName().toLowerCase(); + if (name.endsWith(CaseMetadata.getFileExtension())) { + CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath())); + CaseNodeData nodeData = new CaseNodeData(metadata); + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); + } + } + } + throw new IOException(String.format("Could not find case metadata file for %s", nodeName)); + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java deleted file mode 100644 index af95b0c5a0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.Dialog; -import java.awt.event.KeyEvent; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.KeyStroke; -import org.openide.windows.WindowManager; - -/** - * This class extends a JDialog and maintains the MultiUserCasesPanel. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -final class MultiUserCasesDialog extends JDialog { - - private static final long serialVersionUID = 1L; - private static final String REVIEW_MODE_TITLE = "Open Multi-User Case"; - private static MultiUserCasesPanel multiUserCasesPanel; - private static MultiUserCasesDialog instance; - - /** - * Gets the instance of the MultiuserCasesDialog. - * - * @return The instance. - */ - static public MultiUserCasesDialog getInstance() { - if(instance == null) { - instance = new MultiUserCasesDialog(); - instance.init(); - } - return instance; - } - - /** - * Constructs a MultiUserCasesDialog object. - */ - private MultiUserCasesDialog() { - super(WindowManager.getDefault().getMainWindow(), - REVIEW_MODE_TITLE, - Dialog.ModalityType.APPLICATION_MODAL); - } - - /** - * Initializes the multi-user cases panel. - */ - private void init() { - getRootPane().registerKeyboardAction( - e -> { - setVisible(false); - }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); - - multiUserCasesPanel = new MultiUserCasesPanel(this); - add(multiUserCasesPanel); - pack(); - setResizable(false); - } - - /** - * Set the dialog visibility. When setting it to visible, the contents will - * refresh. - * - * @param value True or false. - */ - @Override - public void setVisible(boolean value) { - if(value) { - multiUserCasesPanel.refresh(); - } - super.setVisible(value); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java new file mode 100644 index 0000000000..3ed3f15492 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java @@ -0,0 +1,69 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 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.List; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * A root node for displaying MultiUserCaseNodes in a NetBeans Explorer View. + */ +final class MultiUserCasesRootNode extends AbstractNode { + + /** + * Constructs a root node for displaying MultiUserCaseNodes in a NetBeans + * Explorer View. + * + * @param case A list of coordination service node data objects representing + * multi-user cases. + */ + MultiUserCasesRootNode(List cases) { + super(Children.create(new MultiUserCasesRootNodeChildren(cases), true)); + } + + private static class MultiUserCasesRootNodeChildren extends ChildFactory { + + private final List cases; + + MultiUserCasesRootNodeChildren(List cases) { + this.cases = cases; + } + + @Override + protected boolean createKeys(List keys) { + if (cases != null && cases.size() > 0) { + keys.addAll(cases); + } + return true; + } + + @Override + protected Node createNodeForKey(CaseNodeData key) { + return new MultiUserCaseNode(key); + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java deleted file mode 100644 index 1efbffe190..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017-2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.awt.Desktop; -import java.awt.event.ActionEvent; -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.AbstractAction; -import javax.swing.Action; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.NodeProperty; - -/** - * A root node containing child nodes of the multi user cases - */ -final class MultiUserNode extends AbstractNode { - - @Messages({"CaseNode.column.name=Name", - "CaseNode.column.createdTime=Created Time", - "CaseNode.column.metadataFilePath=Path"}) - private static final Logger LOGGER = Logger.getLogger(MultiUserNode.class.getName()); - private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; - - /** - * Provides a root node with children which each represent a case. - * - * @param caseList the list of CaseMetadata objects representing the cases - */ - MultiUserNode(List caseList) { - super(Children.create(new MultiUserNodeChildren(caseList), true)); - } - - static class MultiUserNodeChildren extends ChildFactory { - - private final List caseList; - - MultiUserNodeChildren(List caseList) { - this.caseList = caseList; - } - - @Override - protected boolean createKeys(List list) { - if (caseList != null && caseList.size() > 0) { - list.addAll(caseList); - } - return true; - } - - @Override - protected Node createNodeForKey(CaseMetadata key) { - return new MultiUserCaseNode(key); - } - - } - - /** - * A node which represents a single multi user case. - */ - static final class MultiUserCaseNode extends AbstractNode { - - private final String caseName; - private final String caseCreatedDate; - private final String caseMetadataFilePath; - private final Path caseLogFilePath; - - MultiUserCaseNode(CaseMetadata multiUserCase) { - super(Children.LEAF); - caseName = multiUserCase.getCaseDisplayName(); - caseCreatedDate = multiUserCase.getCreatedDate(); - super.setName(caseName); - setName(caseName); - setDisplayName(caseName); - caseMetadataFilePath = multiUserCase.getFilePath().toString(); - caseLogFilePath = Paths.get(multiUserCase.getCaseDirectory(), LOG_FILE_NAME); - } - - /** - * Returns action to open the Case represented by this node - * @return an action which will open the current case - */ - @Override - public Action getPreferredAction() { - return new OpenMultiUserCaseAction(caseMetadataFilePath); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), - caseName)); - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), - caseCreatedDate)); - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), - caseMetadataFilePath)); - return sheet; - } - - @Override - public Action[] getActions(boolean context) { - List actions = new ArrayList<>(); - actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); //open case context menu option - actions.add(new OpenCaseLogAction(caseLogFilePath)); - return actions.toArray(new Action[actions.size()]); - } - } - - @Messages({"MultiUserNode.OpenMultiUserCaseAction.text=Open Case"}) - /** - * An action that opens the specified case and hides the multi user case - * panel. - */ - private static final class OpenMultiUserCaseAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - - private final String caseMetadataFilePath; - - OpenMultiUserCaseAction(String path) { - super(Bundle.MultiUserNode_OpenMultiUserCaseAction_text()); - caseMetadataFilePath = path; - } - - @Override - public void actionPerformed(ActionEvent e) { - StartupWindowProvider.getInstance().close(); - MultiUserCasesDialog.getInstance().setVisible(false); - new Thread( - () -> { - try { - Case.openAsCurrentCase(caseMetadataFilePath); - } catch (CaseActionException ex) { - if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS - MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage()); - } - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT - StartupWindowProvider.getInstance().open(); - MultiUserCasesDialog.getInstance().setVisible(true); - }); - } - } - ).start(); - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); //To change body of generated methods, choose Tools | Templates. - } - } - - @Messages({"MultiUserNode.OpenCaseLogAction.text=Open Log File"}) - /** - * An action that opens the specified case and hides the multi user case - * panel. - */ - private static final class OpenCaseLogAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - - private final Path pathToLog; - - OpenCaseLogAction(Path caseLogFilePath) { - super(Bundle.MultiUserNode_OpenCaseLogAction_text()); - pathToLog = caseLogFilePath; - this.setEnabled(caseLogFilePath != null && caseLogFilePath.toFile().exists()); - } - - @Override - public void actionPerformed(ActionEvent e) { - - if (pathToLog != null) { - try { - if (pathToLog.toFile().exists()) { - Desktop.getDesktop().edit(pathToLog.toFile()); - - } else { - JOptionPane.showMessageDialog(MultiUserCasesDialog.getInstance(), org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotFindLog"), - org.openide.util.NbBundle.getMessage(MultiUserNode.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(MultiUserCasesDialog.getInstance(), - org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotOpenLog"), - org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.unableToShowLogFile"), - JOptionPane.PLAIN_MESSAGE); - } - } - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); //To change body of generated methods, choose Tools | Templates. - } - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java new file mode 100644 index 0000000000..6fdf6044ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Dialog; +import java.awt.event.KeyEvent; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.KeyStroke; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; + +/** + * A singleton JDialog that allows a user to open a multi-user case. + */ +final class OpenMultiUserCaseDialog extends JDialog { + + private static final long serialVersionUID = 1L; + private static OpenMultiUserCaseDialog instance; + private static OpenMultiUserCasePanel multiUserCasesPanel; + + /** + * Gets the singleton JDialog singleton JDialog that allows a user to open a + * multi-user case. + * + * @return The singleton JDialog instance. + */ + public synchronized static OpenMultiUserCaseDialog getInstance() { + if (instance == null) { + instance = new OpenMultiUserCaseDialog(); + instance.init(); + } + return instance; + } + + /** + * Constructs a singleton JDialog singleton JDialog that allows a user to + * open a multi-user case. + */ + @NbBundle.Messages({ + "OpenMultiUserCaseDialog.title=Open Multi-User Case" + }) + private OpenMultiUserCaseDialog() { + super(WindowManager.getDefault().getMainWindow(), + Bundle.OpenMultiUserCaseDialog_title(), + Dialog.ModalityType.APPLICATION_MODAL); + } + + /** + * Registers a keyboard action to hide the dialog when the escape key is + * pressed and adds a OpenMultiUserCasePanel child component. + */ + private void init() { + getRootPane().registerKeyboardAction( + action -> { + setVisible(false); + }, + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_IN_FOCUSED_WINDOW); + multiUserCasesPanel = new OpenMultiUserCasePanel(this); + add(multiUserCasesPanel); + pack(); + setResizable(false); + } + + /** + * Sets the dialog visibility. When made visible, the dialog refreshes the + * display of its OpenMultiUserCasePanel child component. + * + * @param value True or false. + */ + @Override + public void setVisible(boolean value) { + if (value) { + multiUserCasesPanel.refreshDisplay(); + } + super.setVisible(value); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form similarity index 71% rename from Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form rename to Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form index 71810b7387..8f6f0e138b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form @@ -28,12 +28,10 @@ - + - - - + @@ -48,7 +46,6 @@ - @@ -58,30 +55,10 @@ - - - - - - - - - - - - - - - - - - - - - + @@ -97,7 +74,7 @@ - + @@ -116,7 +93,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java similarity index 59% rename from Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java rename to Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java index 07d863bc2b..6c533a6ff6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2017-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,107 +18,52 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.Cursor; import java.util.ArrayList; import java.util.Date; -import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.SortOrder; -import javax.swing.SwingUtilities; -import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableRowSorter; import org.openide.util.Lookup; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** - * A panel that allows a user to open cases created by auto ingest. + * A JPanel that allows a user to open a multi-user case. */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -final class MultiUserCasesPanel extends JPanel{ +@SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives +final class OpenMultiUserCasePanel extends JPanel { - private static final Logger logger = Logger.getLogger(MultiUserCasesPanel.class.getName()); private static final long serialVersionUID = 1L; private final JDialog parentDialog; - private final CaseBrowser caseBrowserPanel; + private final MultiUserCasesBrowserPanel caseBrowserPanel; /** - * Constructs a panel that allows a user to open cases created by automated - * ingest. + * Constructs a JPanel that allows a user to open a multi-user case. + * + * @param parentDialog The parent dialog of the panel, may be null. If + * provided, the dialog is hidden when this poanel's + * cancel button is pressed. */ - MultiUserCasesPanel(JDialog parentDialog) { + OpenMultiUserCasePanel(JDialog parentDialog) { this.parentDialog = parentDialog; initComponents(); - - caseBrowserPanel = new CaseBrowser(); + caseBrowserPanel = new MultiUserCasesBrowserPanel(); caseExplorerScrollPane.add(caseBrowserPanel); caseExplorerScrollPane.setViewportView(caseBrowserPanel); - /* - * Listen for row selection changes and set button state for the current - * selection. - */ - caseBrowserPanel.addListSelectionListener((ListSelectionEvent e) -> { - setButtons(); - }); - } /** - * Gets the list of cases known to the review mode cases manager and - * refreshes the cases table. + * Refreshes the child component that displays the multi-user cases known to + * the coordination service.. */ - void refresh() { - caseBrowserPanel.refresh(); + void refreshDisplay() { + caseBrowserPanel.refreshCases(); } /** - * Enables/disables the Open and Show Log buttons based on the case selected - * in the cases table. - */ - void setButtons() { - bnOpen.setEnabled(caseBrowserPanel.isRowSelected()); - } - - /** - * Open a case. - * - * @param caseMetadataFilePath The path to the case metadata file. - */ - private void openCase(String caseMetadataFilePath) { - if (caseMetadataFilePath != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - - StartupWindowProvider.getInstance().close(); - if (parentDialog != null) { - parentDialog.setVisible(false); - } - new Thread(() -> { - try { - Case.openAsCurrentCase(caseMetadataFilePath); - } catch (CaseActionException ex) { - if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS - MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage()); - } - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT - StartupWindowProvider.getInstance().open(); - }); - } finally { - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - } - }).start(); - } - } - - /** - * RowSorter which makes columns whose type is Date to be sorted first in - * Descending order then in Ascending order + * A RowSorter which causes columns whose type is Date to be sorted in + * either descending order or ascending order with descending order as the + * default. */ private static class RowSorter extends TableRowSorter { @@ -155,7 +100,6 @@ final class MultiUserCasesPanel extends JPanel{ // //GEN-BEGIN:initComponents private void initComponents() { - bnOpen = new javax.swing.JButton(); bnOpenSingleUserCase = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); searchLabel = new javax.swing.JLabel(); @@ -164,18 +108,7 @@ final class MultiUserCasesPanel extends JPanel{ setName("Completed Cases"); // NOI18N setPreferredSize(new java.awt.Dimension(960, 485)); - org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpen.text")); // NOI18N - bnOpen.setEnabled(false); - bnOpen.setMaximumSize(new java.awt.Dimension(80, 23)); - bnOpen.setMinimumSize(new java.awt.Dimension(80, 23)); - bnOpen.setPreferredSize(new java.awt.Dimension(80, 23)); - bnOpen.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - bnOpenActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpenSingleUserCase.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.bnOpenSingleUserCase.text")); // NOI18N bnOpenSingleUserCase.setMinimumSize(new java.awt.Dimension(156, 23)); bnOpenSingleUserCase.setPreferredSize(new java.awt.Dimension(156, 23)); bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() { @@ -184,7 +117,7 @@ final class MultiUserCasesPanel extends JPanel{ } }); - org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.cancelButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.cancelButton.text")); // NOI18N cancelButton.setMaximumSize(new java.awt.Dimension(80, 23)); cancelButton.setMinimumSize(new java.awt.Dimension(80, 23)); cancelButton.setPreferredSize(new java.awt.Dimension(80, 23)); @@ -194,7 +127,7 @@ final class MultiUserCasesPanel extends JPanel{ } }); - org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.searchLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.searchLabel.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -206,17 +139,12 @@ final class MultiUserCasesPanel extends JPanel{ .addComponent(caseExplorerScrollPane) .addGroup(layout.createSequentialGroup() .addComponent(searchLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 350, Short.MAX_VALUE) .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(190, 190, 190) - .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) ); - - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnOpen, cancelButton}); - layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -225,7 +153,6 @@ final class MultiUserCasesPanel extends JPanel{ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) @@ -233,14 +160,10 @@ final class MultiUserCasesPanel extends JPanel{ }// //GEN-END:initComponents /** - * Open button action + * Opens the standard open single user case window. * - * @param evt -- The event that caused this to be called + * @param evt An ActionEvent, unused. */ - private void bnOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenActionPerformed - openCase(caseBrowserPanel.getCasePath()); - }//GEN-LAST:event_bnOpenActionPerformed - private void bnOpenSingleUserCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenSingleUserCaseActionPerformed Lookup.getDefault().lookup(CaseOpenAction.class).openCaseSelectionWindow(); }//GEN-LAST:event_bnOpenSingleUserCaseActionPerformed @@ -252,7 +175,6 @@ final class MultiUserCasesPanel extends JPanel{ }//GEN-LAST:event_cancelButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton bnOpen; private javax.swing.JButton bnOpenSingleUserCase; private javax.swing.JButton cancelButton; private javax.swing.JScrollPane caseExplorerScrollPane; diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java index 0b220e04b2..7b10f80c55 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2017-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,67 +20,129 @@ package org.sleuthkit.autopsy.coordinationservice; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Date; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; /** - * An object that converts case data for a case directory coordination service + * An object that converts data for a case directory lock coordination service * node to and from byte arrays. */ public final class CaseNodeData { - private static final int CURRENT_VERSION = 0; - + private static final int CURRENT_VERSION = 1; + + /* + * Version 0 fields. + */ private int version; private boolean errorsOccurred; + /* + * Version 1 fields. + */ + private Path directory; + private long createDate; + private long lastAccessDate; + private String name; + private String displayName; + private short deletedItemFlags; + /** - * Gets the current version of the case directory coordination service node - * data. + * Gets the current version of the case directory lock coordination service + * node data. * * @return The version number. */ public static int getCurrentVersion() { return CaseNodeData.CURRENT_VERSION; } - + + /** + * Uses a CaseMetadata object to construct an object that converts data for + * a case directory lock coordination service node to and from byte arrays. + * + * @param metadata The case meta data. + * + * @throws java.text.ParseException If there is an error parsing dates from + * string representations of dates in the + * meta data. + */ + public CaseNodeData(CaseMetadata metadata) throws ParseException { + this.version = CURRENT_VERSION; + this.errorsOccurred = false; + this.directory = Paths.get(metadata.getCaseDirectory()); + this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate()).getTime(); + this.lastAccessDate = new Date().getTime(); // Don't really know. + this.name = metadata.getCaseName(); + this.displayName = metadata.getCaseDisplayName(); + this.deletedItemFlags = 0; + } + /** * Uses coordination service node data to construct an object that converts - * case data for a case directory coordination service node to and from byte + * data for a case directory lock coordination service node to and from byte * arrays. * * @param nodeData The raw bytes received from the coordination service. - * + * * @throws InvalidDataException If the node data buffer is smaller than * expected. */ public CaseNodeData(byte[] nodeData) throws InvalidDataException { - if(nodeData == null || nodeData.length == 0) { - this.version = CURRENT_VERSION; - this.errorsOccurred = false; - } else { - /* - * Get fields from node data. - */ - ByteBuffer buffer = ByteBuffer.wrap(nodeData); - try { - if (buffer.hasRemaining()) { - this.version = buffer.getInt(); + if (nodeData == null || nodeData.length == 0) { + throw new InvalidDataException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array"); + } - /* - * Flags bit format: 76543210 - * 0-6 --> reserved for future use - * 7 --> errorsOccurred - */ - byte flags = buffer.get(); - this.errorsOccurred = (flags < 0); - } - } catch (BufferUnderflowException ex) { - throw new InvalidDataException("Node data is incomplete", ex); + /* + * Get the fields from the node data. + */ + ByteBuffer buffer = ByteBuffer.wrap(nodeData); + try { + /* + * Get version 0 fields. + */ + this.version = buffer.getInt(); + + /* + * Flags bit format: 76543210 0-6 --> reserved for future use 7 --> + * errorsOccurred + */ + byte flags = buffer.get(); + this.errorsOccurred = (flags < 0); + + if (buffer.hasRemaining()) { + /* + * Get version 1 fields. + */ + this.directory = Paths.get(NodeDataUtils.getStringFromBuffer(buffer)); + this.createDate = buffer.getLong(); + this.lastAccessDate = buffer.getLong(); + this.name = NodeDataUtils.getStringFromBuffer(buffer); + this.displayName = NodeDataUtils.getStringFromBuffer(buffer); + this.deletedItemFlags = buffer.getShort(); } + + } catch (BufferUnderflowException ex) { + throw new InvalidDataException("Node data is incomplete", ex); } } /** - * Gets whether or not any errors occurred during the processing of the job. + * Gets the node data version number of this node. + * + * @return The version number. + */ + public int getVersion() { + return this.version; + } + + /** + * Gets whether or not any errors occurred during the processing of any auto + * ingest job for the case represented by this node data. * * @return True or false. */ @@ -89,7 +151,8 @@ public final class CaseNodeData { } /** - * Sets whether or not any errors occurred during the processing of job. + * Sets whether or not any errors occurred during the processing of any auto + * ingest job for the case represented by this node data. * * @param errorsOccurred True or false. */ @@ -98,14 +161,99 @@ public final class CaseNodeData { } /** - * Gets the node data version number. + * Gets the path of the case directory of the case represented by this node + * data. * - * @return The version number. + * @return The case directory path. */ - public int getVersion() { - return this.version; + public Path getDirectory() { + return this.directory; } - + + /** + * Sets the path of the case directory of the case represented by this node + * data. + * + * @param caseDirectory The case directory path. + */ + public void setDirectory(Path caseDirectory) { + this.directory = caseDirectory; + } + + /** + * Gets the date the case represented by this node data was created. + * + * @return The create date. + */ + public Date getCreateDate() { + return new Date(this.createDate); + } + + /** + * Sets the date the case represented by this node data was created. + * + * @param createDate The create date. + */ + public void setCreateDate(Date createDate) { + this.createDate = createDate.getTime(); + } + + /** + * Gets the date the case represented by this node data last accessed. + * + * @return The last access date. + */ + public Date getLastAccessDate() { + return new Date(this.lastAccessDate); + } + + /** + * Sets the date the case represented by this node data was last accessed. + * + * @param lastAccessDate The last access date. + */ + public void setLastAccessDate(Date lastAccessDate) { + this.lastAccessDate = lastAccessDate.getTime(); + } + + /** + * Gets the unique and immutable (user cannot change it) name of the case + * represented by this node data. + * + * @return The case name. + */ + public String getName() { + return this.name; + } + + /** + * Sets the unique and immutable (user cannot change it) name of the case + * represented by this node data. + * + * @param name The case name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the display name of the case represented by this node data. + * + * @return The case display name. + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Sets the display name of the case represented by this node data. + * + * @param displayName The case display name. + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + /** * Gets the node data as a byte array that can be sent to the coordination * service. @@ -113,16 +261,31 @@ public final class CaseNodeData { * @return The node data as a byte array. */ public byte[] toArray() { - ByteBuffer buffer = ByteBuffer.allocate(5); - + int bufferSize = Integer.BYTES; // version + bufferSize += 1; // errorsOccurred + bufferSize += this.directory.toString().getBytes().length; // directory + bufferSize += Long.BYTES; // createDate + bufferSize += Long.BYTES; // lastAccessDate + bufferSize += this.name.getBytes().length; // name + bufferSize += this.displayName.getBytes().length; // displayName + bufferSize += Short.BYTES; // deletedItemFlags + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + buffer.putInt(this.version); - buffer.put((byte)(this.errorsOccurred ? 0x80 : 0)); - - // Prepare the array + buffer.put((byte) (this.errorsOccurred ? 0x80 : 0)); + + if (this.version >= 1) { + NodeDataUtils.putStringIntoBuffer(this.directory.toString(), buffer); + buffer.putLong(this.createDate); + buffer.putLong(this.lastAccessDate); + NodeDataUtils.putStringIntoBuffer(name, buffer); + NodeDataUtils.putStringIntoBuffer(displayName, buffer); + buffer.putShort(deletedItemFlags); + } + byte[] array = new byte[buffer.position()]; buffer.rewind(); buffer.get(array, 0, array.length); - return array; } diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java new file mode 100755 index 0000000000..f13c2be677 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java @@ -0,0 +1,35 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.coordinationservice; + +/** + * Exception thrown when invlaid coordniation service node data is encountered. + */ +public final class InvalidNodeDataException extends Exception { + + private static final long serialVersionUID = 1L; + + private InvalidNodeDataException(String message) { + super(message); + } + + private InvalidNodeDataException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java new file mode 100755 index 0000000000..cfdb5a5d51 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java @@ -0,0 +1,64 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.coordinationservice; + +import java.nio.ByteBuffer; +import javax.lang.model.type.TypeKind; + +/** + * Utilities for reading and writing node data. + */ +public final class NodeDataUtils { + + /** + * This method retrieves a string from a given buffer. Assumes the string + * was written using putStringIntoBuffer and first reads the the length of + * the string. + * + * @param buffer The buffer from which the string will be read. + * + * @return The string read from the buffer. + */ + public static String getStringFromBuffer(ByteBuffer buffer) { + String output = ""; + int length = buffer.getInt(); + if (length > 0) { + byte[] array = new byte[length]; + buffer.get(array, 0, length); + output = new String(array); + } + return output; + } + + /** + * This method puts a given string into a given buffer. The length of the + * string will be inserted prior to the string. + * + * @param stringValue The string to write to the buffer. + * @param buffer The buffer to which the string will be written. + */ + public static void putStringIntoBuffer(String stringValue, ByteBuffer buffer) { + buffer.putInt(stringValue.getBytes().length); + buffer.put(stringValue.getBytes()); + } + + private NodeDataUtils() { + } + +} From 6c112cbfca0deaddbb9ef23effb70b8edb974325 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 24 Jan 2019 17:26:26 -0500 Subject: [PATCH 2/9] Interim checkin for open multi-user dialog, case node data, case changes --- .../autopsy/casemodule/Bundle.properties | 13 +- .../autopsy/casemodule/Bundle_ja.properties | 6 +- .../sleuthkit/autopsy/casemodule/Case.java | 505 ++++++++++-------- .../CaseNodeData.java | 123 ++--- .../MulitUserCaseNodeDataCollector.java | 164 ++++++ .../autopsy/casemodule/MultiUserCaseNode.java | 31 +- .../MultiUserCasesBrowserPanel.java | 147 +---- .../casemodule/MultiUserCasesRootNode.java | 23 +- .../casemodule/OpenMultiUserCaseDialog.java | 1 + .../casemodule/OpenMultiUserCasePanel.java | 2 +- .../InvalidNodeDataException.java | 35 -- .../coordinationservice/NodeDataUtils.java | 64 --- .../DataResultViewerThumbnail.java | 2 +- .../autoingest/AutoIngestManager.java | 28 +- 14 files changed, 554 insertions(+), 590 deletions(-) rename Core/src/org/sleuthkit/autopsy/{coordinationservice => casemodule}/CaseNodeData.java (66%) create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java delete mode 100755 Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java delete mode 100755 Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index d6b8aeb280..c0fe06e728 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -78,13 +78,12 @@ 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. Case.exception.errGetRootObj=Error getting root objects. -Case.createCaseDir.exception.existNotDir=Cannot create case dir, already exists and is not a directory\: {0} -Case.createCaseDir.exception.existCantRW=Cannot create case dir, already exists and cannot read/write\: {0} +Case.createCaseDir.exception.existNotDir=Cannot create case directory, it already exists and is not a directory\: {0} +Case.createCaseDir.exception.existCantRW=Cannot create case directory, it already exists and cannot read/write\: {0} Case.createCaseDir.exception.cantCreate=Cannot create case directory or it already exists\: {0} Case.createCaseDir.exception.cantCreateCaseDir=Could not create case directory\: {0} Case.createCaseDir.exception.cantCreateModDir=Could not create modules output directory\: {0} Case.createCaseDir.exception.cantCreateReportsDir=Could not create reports output directory\: {0} -Case.createCaseDir.exception.gen=Could not create case directory\: {0} Case.CollaborationSetup.FailNotify.ErrMsg=Failed to connect to any other nodes that may be collaborating on this case. Case.CollaborationSetup.FailNotify.Title=Connection Failure Case.GetCaseTypeGivenPath.Failure=Unable to get case type @@ -192,7 +191,6 @@ ReviewModeCasePanel.LastAccessedTimeHeaderText=Last Accessed Time ReviewModeCasePanel.MetadataFileHeaderText=Metadata File OpenMultiUserCasePanel.jLabel1.text=Recent Cases OpenMultiUserCasePanel.openButton.text=Open -OpenMultiUserCasePanel.cancelButton.text=Cancel CueBannerPanel.newCaseLabel.text=New Case CueBannerPanel.openCaseButton.text= CueBannerPanel.openCaseLabel.text=Open Case @@ -237,6 +235,9 @@ ImageFilePanel.md5HashTextField.text= ImageFilePanel.errorLabel.text=Error Label ImageFilePanel.hashValuesNoteLabel.text=NOTE: These values will not be validated when the data source is added. ImageFilePanel.hashValuesLabel.text=Hash Values (optional): -OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name - OpenMultiUserCasePanel.bnOpenSingleUserCase.text=Open Single-User Case... +# 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. +OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name +OpenMultiUserCasePanel.cancelButton.text=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 208c5ed868..e20b33d82a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -65,7 +65,6 @@ Case.createCaseDir.exception.existNotDir=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u3 Case.createCaseDir.exception.existCantRW=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u65e2\u306b\u5b58\u5728\u3057\u3001\u8aad\u307f\u53d6\u308a\uff0f\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u307e\u305b\u3093\uff1a{0} Case.createCaseDir.exception.cantCreateCaseDir=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a {0} Case.createCaseDir.exception.cantCreateModDir=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} -Case.createCaseDir.exception.gen=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} CaseDeleteAction.closeConfMsg.text=\u3053\u306e\u30b1\u30fc\u30b9\u3092\u672c\u5f53\u306b\u9589\u3058\u3001\u524a\u9664\u3057\u307e\u3059\u304b\uff1f\n\ \u30b1\u30fc\u30b9\u540d\uff1a {0}\n\ \u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\: {1} @@ -132,7 +131,6 @@ AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=\u30ad\u30e3\u30f3\u30bb\u30e LocalFilesPanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb ImageFilePanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb NewCaseVisualPanel1.caseTypeLabel.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\uff1a -Case.databaseConnectionInfo.error.msg=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b5\u30fc\u30d0\u30fc\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u624b\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30c4\u30fc\u30eb\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u3001\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Case.open.exception.multiUserCaseNotEnabled=\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u306e\u30b1\u30fc\u30b9\u304c\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u306a\u3044\u3068\u3001\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u306e\u30b1\u30fc\u30b9\u306f\u958b\u3051\u307e\u305b\u3093\u3002\u30c4\u30fc\u30eb\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u3001\u8907\u6570\u30e6\u30fc\u30b6\u30fc\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Case.createCaseDir.exception.cantCreateReportsDir=\u30ec\u30dd\u30fc\u30c8\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} Case.CollaborationSetup.FailNotify.ErrMsg=\u3053\u306e\u30b1\u30fc\u30b9\u3067\u4f7f\u308f\u308c\u3066\u3044\u308b\u304b\u3082\u3057\u308c\u306a\u3044\u30ce\u30fc\u30c9\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 @@ -180,7 +178,6 @@ OptionalCasePropertiesPanel.caseDisplayNameLabel.text=\u30b1\u30fc\u30b9\u756a\u CueBannerPanel.openRecentCaseLabel.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f CueBannerPanel.openAutoIngestCaseLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f OpenMultiUserCasePanel.openButton.text=\u958b\u304f -OpenMultiUserCasePanel.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb OpenMultiUserCasePanel.jLabel1.text=\u6700\u8fd1\u958b\u3044\u305f\u30d5\u30a1\u30a4\u30eb CueBannerPanel.newCaseLabel.text=\u65b0\u898f\u30b1\u30fc\u30b9\u3092\u4f5c\u6210 CueBannerPanel.openCaseLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f @@ -200,4 +197,5 @@ LogicalEvidenceFilePanel.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb LogicalEvidenceFilePanel.logicalEvidenceFileChooser.dialogTitle=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u307e\u305f\u306f\u30d5\u30a9\u30eb\u30c0\u3092\u9078\u629e LogicalEvidenceFilePanel.logicalEvidenceFileChooser.approveButtonText=\u9078\u629e LocalDiskSelectionDialog.errorLabel.text=\u30a8\u30e9\u30fc\u30e9\u30d9\u30eb -LocalDiskSelectionDialog.selectLocalDiskLabel.text=\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30b9\u30af\u3092\u9078\u629e\uff1a \ No newline at end of file +LocalDiskSelectionDialog.selectLocalDiskLabel.text=\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30b9\u30af\u3092\u9078\u629e\uff1a +OpenMultiUserCasePanel.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 44f1a893af..777f1472ef 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -58,7 +58,6 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; 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; @@ -82,7 +81,6 @@ import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchAction; import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; @@ -892,72 +890,71 @@ public class Case { /** * 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. + * @param caseDirPath 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 */ - public 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)); + public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException { + /* + * Check the case directory path and permissions. The case directory may + * already exist. + */ + File caseDir = new File(caseDirPath); + if (caseDir.exists()) { + if (caseDir.isFile()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath)); + } else if (!caseDir.canRead() || !caseDir.canWrite()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath)); } } - try { - boolean result = (caseDirF).mkdirs(); // create root case Directory + /* + * Create the case directory, if it does not already exist. + */ + if (!caseDir.mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath)); + } - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir)); - } + /* + * Create the subdirectories of the case directory, if they do not + * already exist. Note that multi-user cases get an extra layer of + * subdirectories, one subdirectory per application host machine. + */ + String hostPathComponent = ""; + if (caseType == CaseType.MULTI_USER_CASE) { + hostPathComponent = File.separator + NetworkUtils.getLocalHostName(); + } - // create the folders inside the case directory - String hostClause = ""; + Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER); + if (!exportDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir)); + } - 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(); + Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER); + if (!logsDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir)); + } - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir)); - } + Path tempDir = Paths.get(caseDirPath, hostPathComponent, TEMP_FOLDER); + if (!tempDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", tempDir)); + } - final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER; - result = new File(modulesOutDir).mkdir(); + Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER); + if (!cacheDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir)); + } - if (result == false) { - throw new CaseActionException( - NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", - modulesOutDir)); - } + Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER); + if (!moduleOutputDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir)); + } - 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); + Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER); + if (!reportsDir.toFile().mkdirs()) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir)); } } @@ -1947,39 +1944,44 @@ public class Case { * @param isNewCase True for a new case, false otherwise. * @param progressIndicator A progress indicator. * - * @throws CaseActionException if there is a problem creating the case. The + * @throws CaseActionException If there is a problem creating the case. The * exception will have a user-friendly message * and may be a wrapper for a lower-level * exception. */ private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException { try { - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - + checkForUserCancellation(); + createCaseDirectoryIfDoesNotExist(progressIndicator); + checkForUserCancellation(); + switchLoggingToCaseLogsDirectory(progressIndicator); + checkForUserCancellation(); if (isNewCase) { - createCaseData(progressIndicator); // RJCTODO: This name is vague + createCaseNodeData(progressIndicator); } else { - openCaseData(progressIndicator); // RJCTODO: This name is vague + updateCaseNodeData(progressIndicator); } - - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - openServices(progressIndicator); - - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + checkForUserCancellation(); + deleteTempfilesFromCaseDirectory(progressIndicator); + checkForUserCancellation(); + if (isNewCase) { + createCaseDatabase(progressIndicator); + } else { + openCaseDataBase(progressIndicator); } + checkForUserCancellation(); + openCaseLevelServices(progressIndicator); + checkForUserCancellation(); + openAppServiceCaseResources(progressIndicator); + checkForUserCancellation(); + openCommunicationChannels(progressIndicator); } catch (CaseActionException ex) { /* - * Cancellation or failure. Clean up. The sleep is a little hack to - * clear the interrupted flag for this thread if this is a - * cancellation scenario, so that the clean up can run to completion - * in this thread. + * Cancellation or failure. Clean up by calling the close method. + * The sleep is a little hack to clear the interrupted flag for this + * thread if this is a cancellation scenario, so that the clean up + * can run to completion in the current thread. */ try { Thread.sleep(1); @@ -1991,60 +1993,150 @@ public class Case { } /** - * Creates the case directory, case database, and case metadata file. + * Checks current thread for an interrupt. Usage: checking for user + * cancellation of a case creation/opening operation, as reflected in the + * exception message. * - * @param progressIndicator A progress indicartor. + * @throws CaseActionCancelledException If the current thread is + * interrupted, assumes interrupt was + * due to a user action. + */ + private static void checkForUserCancellation() throws CaseActionCancelledException { + if (Thread.currentThread().isInterrupted()) { + throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + } + } + + /** + * Creates the case directory, if it does not already exist. * - * @throws CaseActionException If there is a problem creating the case - * database. The exception will have a + * TODO (JIRA-2180): Always create the case directory as part of the case + * creation process. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a * user-friendly message and may be a wrapper * for a lower-level exception. */ @Messages({ - "Case.progressMessage.creatingCaseDirectory=Creating case directory...", - "Case.progressMessage.creatingCaseDatabase=Creating case database...", - "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}", - "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file." - }) - private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException { - // RJCTODO: progress - try { - CoordinationService coordinationService = CoordinationService.getInstance(); - CaseNodeData nodeData = new CaseNodeData(metadata); - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException | ParseException ex) { - // RJCTODO - } - - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Create the case directory, if it does not already exist. - * - * TODO (JIRA-2180): Always create the case directory as part of the - * case creation process. - */ + "Case.progressMessage.creatingCaseDirectory=Creating case directory...",}) + private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); if (new File(metadata.getCaseDirectory()).exists() == false) { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType()); } + } - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + /** + * Switches from writing log messages to the application logs to the logs + * subdirectory of the case directory. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.switchingLogDirectory=Switching log directory..." + }) + private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) { + progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); + Logger.setLogDirectory(getLogDirectoryPath()); + } + + /** + * Creates the node data for the case directory lock coordination service + * node. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...", + "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}." + }) + private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData()); + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(metadata); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex); } + } + /** + * Updates the node data for the case directory lock coordination service + * node. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...", + "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}." + }) + private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData()); + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setLastAccessDate(new Date()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + } + } + + /** + * Deletes any files in the temp subdirectory of the case directory. + * + * @param progressIndicator A progress indicator. + */ + @Messages({ + "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",}) + private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) { /* - * Create the case database. + * Clear the temp subdirectory of the case directory. */ + progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory()); + Case.clearTempSubDir(this.getTempDirectory()); + } + + /** + * Creates the node data for the case directory lock coordination service + * node, the case directory, the case database and the case metadata file. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.creatingCaseDatabase=Creating case database...", + "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}." + }) + private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); try { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { /* * For single-user cases, the case database is a SQLite database - * with a standard name, physically located in the root of the - * case directory. + * with a standard name, physically located in the case + * directory. */ caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString()); metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME); @@ -2052,7 +2144,7 @@ public class Case { /* * For multi-user cases, the case database is a PostgreSQL * database with a name derived from the case display name, - * physically located on a database server. + * physically located on the PostgreSQL database server. */ caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); metadata.setCaseDatabaseName(caseDb.getDatabaseName()); @@ -2060,161 +2152,81 @@ public class Case { } catch (TskCoreException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex); } catch (UserPreferencesException ex) { - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex); } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex); } } /** - * Opens an existing case database. + * Updates the node data for an existing case directory lock coordination + * service node and opens an existing case database. * * @param progressIndicator A progress indicator. * - * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @throws CaseActionException If there is a problem completing the + * operation. 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.", - "Case.unsupportedSchemaVersionMessage=Unsupported DB schema version - see log for details", - "Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-User.", - "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. " - + "See Tools, Options, Multi-user." + "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.", + "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User." }) - private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException { + private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); try { - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - // RJCTODO: progress - try { - CoordinationService coordinationService = CoordinationService.getInstance(); - CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); - nodeData.setLastAccessDate(new Date()); - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException | CaseNodeData.InvalidDataException ex) { - // RJCTODO - } - - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); String databaseName = metadata.getCaseDatabaseName(); if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString()); } else if (UserPreferences.getIsMultiUserModeEnabled()) { - try { - caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); - } catch (UserPreferencesException ex) { - throw new CaseActionException(Case_databaseConnectionInfo_error_msg(), ex); - } + caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); } else { throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled()); } } catch (TskUnsupportedSchemaVersionException ex) { - throw new CaseActionException(Bundle.Case_unsupportedSchemaVersionMessage(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex); + } catch (UserPreferencesException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex); } catch (TskCoreException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex); } } /** - * Completes the case opening tasks common to both new cases and existing - * cases. + * Opens the case-level services: the files manager, tags manager and + * blackboard. * * @param progressIndicator A progress indicator. - * - * @throws CaseActionException */ @Messages({ - "Case.progressMessage.switchingLogDirectory=Switching log directory...", - "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", - "Case.progressMessage.openingCaseLevelServices=Opening case-level services...", - "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...", - "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",}) - private void openServices(ProgressIndicator progressIndicator) throws CaseActionException { - /* - * Switch to writing to the application logs in the logs subdirectory of - * the case directory. - */ - progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory()); - Logger.setLogDirectory(getLogDirectoryPath()); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Clear the temp subdirectory of the case directory. - */ - progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory()); - Case.clearTempSubDir(this.getTempDirectory()); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Open the case-level services. - */ + "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",}) + private void openCaseLevelServices(ProgressIndicator progressIndicator) { progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices()); this.caseServices = new Services(caseDb); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * Allow any registered application services to open any resources - * specific to this case. - */ - progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); - openAppServiceCaseResources(); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - - /* - * If this case is a multi-user case, set up for communication with - * other nodes. - */ - if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { - progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); - try { - eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName())); - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); - } - collaborationMonitor = new CollaborationMonitor(metadata.getCaseName()); - } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) { - /* - * The collaboration monitor and event channel are not - * essential. Log an error and notify the user, but do not - * throw. - */ - logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), - NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"))); - } - } - } } /** * Allows any registered application-level services to open resources * specific to this case. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. */ @NbBundle.Messages({ + "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...", "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources", "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...", "# {0} - service name", "Case.servicesException.notificationTitle={0} Error" }) - private void openAppServiceCaseResources() throws CaseActionException { + private void openAppServiceCaseResources(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); /* * Each service gets its own independently cancellable/interruptible * task, running in a named thread managed by an executor service, with @@ -2230,20 +2242,20 @@ public class Case { * with a Cancel button. */ CancelButtonListener cancelButtonListener = null; - ProgressIndicator progressIndicator; + ProgressIndicator appServiceProgressIndicator; if (RuntimeProperties.runningWithGUI()) { cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName())); - progressIndicator = new ModalDialogProgressIndicator( + appServiceProgressIndicator = new ModalDialogProgressIndicator( mainFrame, Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()), new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, Bundle.Case_progressIndicatorCancelButton_label(), cancelButtonListener); } else { - progressIndicator = new LoggingProgressIndicator(); + appServiceProgressIndicator = new LoggingProgressIndicator(); } - progressIndicator.start(Bundle.Case_progressMessage_preparing()); - AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator); + appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing()); + AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator); String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS threadNameSuffix = threadNameSuffix.toLowerCase(); TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix)); @@ -2299,17 +2311,50 @@ public class Case { * task responded to a cancellation request. */ ThreadUtils.shutDownTaskExecutor(executor); - progressIndicator.finish(); + appServiceProgressIndicator.finish(); } + checkForUserCancellation(); + } + } - if (Thread.currentThread().isInterrupted()) { - throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); + /** + * If this case is a multi-user case, sets up for communication with other + * application nodes. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...", + "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}." + }) + private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException { + if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) { + progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); + try { + eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName())); + checkForUserCancellation(); + collaborationMonitor = new CollaborationMonitor(metadata.getCaseName()); + } catch (AutopsyEventException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex); + } catch (CollaborationMonitor.CollaborationMonitorException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex); } } } /** * Closes the case. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. */ private void close() throws CaseActionException { /* diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNodeData.java similarity index 66% rename from Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java rename to Core/src/org/sleuthkit/autopsy/casemodule/CaseNodeData.java index 7b10f80c55..19b134145e 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNodeData.java @@ -16,13 +16,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.coordinationservice; +package org.sleuthkit.autopsy.casemodule; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.DateFormat; import java.text.ParseException; import java.util.Date; import org.sleuthkit.autopsy.casemodule.CaseMetadata; @@ -45,8 +47,8 @@ public final class CaseNodeData { * Version 1 fields. */ private Path directory; - private long createDate; - private long lastAccessDate; + private Date createDate; + private Date lastAccessDate; private String name; private String displayName; private short deletedItemFlags; @@ -75,8 +77,8 @@ public final class CaseNodeData { this.version = CURRENT_VERSION; this.errorsOccurred = false; this.directory = Paths.get(metadata.getCaseDirectory()); - this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate()).getTime(); - this.lastAccessDate = new Date().getTime(); // Don't really know. + this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate()); + this.lastAccessDate = new Date(); this.name = metadata.getCaseName(); this.displayName = metadata.getCaseDisplayName(); this.deletedItemFlags = 0; @@ -89,45 +91,27 @@ public final class CaseNodeData { * * @param nodeData The raw bytes received from the coordination service. * - * @throws InvalidDataException If the node data buffer is smaller than - * expected. + * @throws IOException If there is an error reading the node data. */ - public CaseNodeData(byte[] nodeData) throws InvalidDataException { + public CaseNodeData(byte[] nodeData) throws IOException { if (nodeData == null || nodeData.length == 0) { - throw new InvalidDataException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array"); + throw new IOException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array"); } - - /* - * Get the fields from the node data. - */ - ByteBuffer buffer = ByteBuffer.wrap(nodeData); - try { - /* - * Get version 0 fields. - */ - this.version = buffer.getInt(); - - /* - * Flags bit format: 76543210 0-6 --> reserved for future use 7 --> - * errorsOccurred - */ - byte flags = buffer.get(); - this.errorsOccurred = (flags < 0); - - if (buffer.hasRemaining()) { - /* - * Get version 1 fields. - */ - this.directory = Paths.get(NodeDataUtils.getStringFromBuffer(buffer)); - this.createDate = buffer.getLong(); - this.lastAccessDate = buffer.getLong(); - this.name = NodeDataUtils.getStringFromBuffer(buffer); - this.displayName = NodeDataUtils.getStringFromBuffer(buffer); - this.deletedItemFlags = buffer.getShort(); - } - - } catch (BufferUnderflowException ex) { - throw new InvalidDataException("Node data is incomplete", ex); + DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(nodeData)); + this.version = inputStream.readInt(); + if (this.version > 0) { + this.errorsOccurred = inputStream.readBoolean(); + } else { + short legacyErrorsOccurred = inputStream.readByte(); + this.errorsOccurred = (legacyErrorsOccurred < 0); + } + if (this.version > 0) { + this.directory = Paths.get(inputStream.readUTF()); + this.createDate = new Date(inputStream.readLong()); + this.lastAccessDate = new Date(inputStream.readLong()); + this.name = inputStream.readUTF(); + this.displayName = inputStream.readUTF(); + this.deletedItemFlags = inputStream.readShort(); } } @@ -186,7 +170,7 @@ public final class CaseNodeData { * @return The create date. */ public Date getCreateDate() { - return new Date(this.createDate); + return new Date(this.createDate.getTime()); } /** @@ -195,7 +179,7 @@ public final class CaseNodeData { * @param createDate The create date. */ public void setCreateDate(Date createDate) { - this.createDate = createDate.getTime(); + this.createDate = new Date(createDate.getTime()); } /** @@ -204,7 +188,7 @@ public final class CaseNodeData { * @return The last access date. */ public Date getLastAccessDate() { - return new Date(this.lastAccessDate); + return new Date(this.lastAccessDate.getTime()); } /** @@ -213,7 +197,7 @@ public final class CaseNodeData { * @param lastAccessDate The last access date. */ public void setLastAccessDate(Date lastAccessDate) { - this.lastAccessDate = lastAccessDate.getTime(); + this.lastAccessDate = new Date(lastAccessDate.getTime()); } /** @@ -259,34 +243,23 @@ public final class CaseNodeData { * service. * * @return The node data as a byte array. + * + * @throws IOException If there is an error writing the node data. */ - public byte[] toArray() { - int bufferSize = Integer.BYTES; // version - bufferSize += 1; // errorsOccurred - bufferSize += this.directory.toString().getBytes().length; // directory - bufferSize += Long.BYTES; // createDate - bufferSize += Long.BYTES; // lastAccessDate - bufferSize += this.name.getBytes().length; // name - bufferSize += this.displayName.getBytes().length; // displayName - bufferSize += Short.BYTES; // deletedItemFlags - ByteBuffer buffer = ByteBuffer.allocate(bufferSize); - - buffer.putInt(this.version); - buffer.put((byte) (this.errorsOccurred ? 0x80 : 0)); - - if (this.version >= 1) { - NodeDataUtils.putStringIntoBuffer(this.directory.toString(), buffer); - buffer.putLong(this.createDate); - buffer.putLong(this.lastAccessDate); - NodeDataUtils.putStringIntoBuffer(name, buffer); - NodeDataUtils.putStringIntoBuffer(displayName, buffer); - buffer.putShort(deletedItemFlags); - } - - byte[] array = new byte[buffer.position()]; - buffer.rewind(); - buffer.get(array, 0, array.length); - return array; + public byte[] toArray() throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream outputStream = new DataOutputStream(byteStream); + outputStream.writeInt(this.version); + outputStream.writeBoolean(this.errorsOccurred); + outputStream.writeUTF(this.directory.toString()); + outputStream.writeLong(this.createDate.getTime()); + outputStream.writeLong(this.lastAccessDate.getTime()); + outputStream.writeUTF(this.name); + outputStream.writeUTF(this.displayName); + outputStream.writeShort(this.deletedItemFlags); + outputStream.flush(); + byteStream.flush(); + return byteStream.toByteArray(); } public final static class InvalidDataException extends Exception { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java new file mode 100755 index 0000000000..4ec14d4650 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java @@ -0,0 +1,164 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.io.File; +import java.io.IOException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Queries the coordination service to collect the multi-user case node data + * stored in the case directory lock ZooKeeper nodes. + */ +final class MulitUserCaseNodeDataCollector { + + private static final Logger logger = Logger.getLogger(MulitUserCaseNodeDataCollector.class.getName()); + private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS + private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS + + /** + * Queries the coordination service to collect the multi-user case node data + * stored in the case directory lock ZooKeeper nodes. + * + * @return A list of CaseNodedata objects that convert data for a case + * directory lock coordination service node to and from byte arrays. + * + * @throws CoordinationServiceException If there is an error + */ + public static List getNodeData() throws CoordinationService.CoordinationServiceException { + final List cases = new ArrayList<>(); + final CoordinationService coordinationService = CoordinationService.getInstance(); + final List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); + for (String nodeName : nodeList) { + /* + * Ignore auto ingest case name lock nodes. + */ + final Path nodeNameAsPath = Paths.get(nodeName); + if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) { + continue; + } + + /* + * Ignore case auto ingest log lock nodes and resource lock nodes. + */ + final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString(); + if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME)) { + continue; + } + + /* + * Ignore case resources lock nodes. + */ + if (lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) { + continue; + } + + /* + * Get the data from the case directory lock node. This data may not + * exist for "legacy" nodes. If it is missing, create it. + */ + try { + CaseNodeData nodeData; + byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName); + if (nodeBytes != null && nodeBytes.length > 0) { + nodeData = new CaseNodeData(nodeBytes); + if (nodeData.getVersion() == 0) { + /* + * Version 0 case node data was only written if errors + * occurred during an auto ingest job and consisted of + * only the set errors flag. + */ + nodeData = createNodeDataFromCaseMetadata(nodeName, true); + } + } else { + nodeData = createNodeDataFromCaseMetadata(nodeName, false); + } + cases.add(nodeData); + + } catch (CoordinationService.CoordinationServiceException | InterruptedException | IOException | ParseException | CaseMetadata.CaseMetadataException ex) { + logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); + } + + } + return cases; + } + + /** + * Creates and saves case directory lock coordination service node data from + * the metadata file for the case associated with the node. + * + * @param nodeName The coordination service node name, i.e., the case + * directory path. + * @param errorsOccurred Whether or not errors occurred during an auto + * ingest job for the case. + * + * @return A CaseNodedata object. + * + * @throws IOException If there is an error writing the + * node data to a byte array. + * @throws CaseMetadataException If there is an error reading the + * case metadata file. + * @throws ParseException If there is an error parsing a date + * from the case metadata file. + * @throws CoordinationServiceException If there is an error interacting + * with the coordination service. + * @throws InterruptedException If a coordination service operation + * is interrupted. + */ + private static CaseNodeData createNodeDataFromCaseMetadata(String nodeName, boolean errorsOccurred) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException { + CaseNodeData nodeData = null; + Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS); + File caseDirectory = caseDirectoryPath.toFile(); + if (caseDirectory.exists()) { + File[] files = caseDirectory.listFiles(); + for (File file : files) { + String name = file.getName().toLowerCase(); + if (name.endsWith(CaseMetadata.getFileExtension())) { + CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath())); + nodeData = new CaseNodeData(metadata); + nodeData.setErrorsOccurred(errorsOccurred); + break; + } + } + } + if (nodeData != null) { + CoordinationService coordinationService = CoordinationService.getInstance(); + coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); + return nodeData; + } else { + throw new IOException(String.format("Could not find case metadata file for %s", nodeName)); + } + } + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private MulitUserCaseNodeDataCollector() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java index 0a762f5f3b..5d801e2d38 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -33,7 +33,12 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; +import org.sleuthkit.autopsy.casemodule.StartupWindowProvider; +import org.sleuthkit.autopsy.casemodule.Bundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.NodeProperty; @@ -66,9 +71,9 @@ final class MultiUserCaseNode extends AbstractNode { } @NbBundle.Messages({ - "CaseNode.column.name=Name", - "CaseNode.column.createTime=Create Time", - "CaseNode.column.path=Path" + "MultiUserCaseNode.column.name=Name", + "MultiUserCaseNode.column.createTime=Create Time", + "MultiUserCaseNode.column.path=Path" }) @Override protected Sheet createSheet() { @@ -78,17 +83,17 @@ final class MultiUserCaseNode extends AbstractNode { sheetSet = Sheet.createPropertiesSet(); sheet.put(sheetSet); } - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(), - Bundle.CaseNode_column_name(), - Bundle.CaseNode_column_name(), + sheetSet.put(new NodeProperty<>(Bundle.MultiUserCaseNode_column_name(), + Bundle.MultiUserCaseNode_column_name(), + Bundle.MultiUserCaseNode_column_name(), caseNodeData.getDisplayName())); - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createTime(), - Bundle.CaseNode_column_createTime(), - Bundle.CaseNode_column_createTime(), + sheetSet.put(new NodeProperty<>(Bundle.MultiUserCaseNode_column_createTime(), + Bundle.MultiUserCaseNode_column_createTime(), + Bundle.MultiUserCaseNode_column_createTime(), caseNodeData.getCreateDate())); - sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_path(), - Bundle.CaseNode_column_path(), - Bundle.CaseNode_column_path(), + sheetSet.put(new NodeProperty<>(Bundle.MultiUserCaseNode_column_path(), + Bundle.MultiUserCaseNode_column_path(), + Bundle.MultiUserCaseNode_column_path(), caseNodeData.getDirectory().toString())); return sheet; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java index 2a99346792..b8522c50d7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java @@ -24,25 +24,10 @@ import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; -import java.awt.EventQueue; -import java.io.File; -import java.io.IOException; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.swing.SwingWorker; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.EmptyNode; -import java.util.concurrent.ExecutionException; import org.openide.explorer.view.OutlineView; +import org.sleuthkit.autopsy.casemodule.Bundle; /** * A JPanel with a scroll pane child component that contains a NetBeans @@ -50,14 +35,12 @@ import org.openide.explorer.view.OutlineView; * to the coordination service. */ @SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives -class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider { +final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(MultiUserCasesBrowserPanel.class.getName()); - private ExplorerManager explorerManager; // RJCTODO: COnsider making this final - private final Outline outline; + private final ExplorerManager explorerManager; private final OutlineView outlineView; - private LoadCaseListWorker loadCaseListWorker; + private final Outline outline; /** * Constructs a JPanel with a scroll pane child component that contains a @@ -65,10 +48,12 @@ class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerM * cases known to the coordination service. */ MultiUserCasesBrowserPanel() { + explorerManager = new ExplorerManager(); outlineView = new org.openide.explorer.view.OutlineView(); initComponents(); outline = outlineView.getOutline(); customizeOutlineView(); + explorerManager.setRootContext(new MultiUserCasesRootNode()); } /** @@ -76,17 +61,17 @@ class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerM */ private void customizeOutlineView() { outlineView.setPropertyColumns( - Bundle.CaseNode_column_createTime(), Bundle.CaseNode_column_createTime(), // RJCTODO: Move these into this file? - Bundle.CaseNode_column_path(), Bundle.CaseNode_column_path()); - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); + Bundle.MultiUserCaseNode_column_createTime(), Bundle.MultiUserCaseNode_column_createTime(), + Bundle.MultiUserCaseNode_column_path(), Bundle.MultiUserCaseNode_column_path()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.MultiUserCaseNode_column_name()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); TableColumnModel columnModel = outline.getColumnModel(); int pathColumnIndex = 0; int dateColumnIndex = 0; for (int index = 0; index < columnModel.getColumnCount(); index++) { - if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_path())) { + if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.MultiUserCaseNode_column_path())) { pathColumnIndex = index; - } else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_createTime())) { + } else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.MultiUserCaseNode_column_createTime())) { dateColumnIndex = index; } } @@ -103,10 +88,6 @@ class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerM */ outline.setColumnSorted(dateColumnIndex, false, 1); - if (null == explorerManager) { - explorerManager = new ExplorerManager(); - } - caseTableScrollPane.setViewportView(outlineView); this.setVisible(true); } @@ -124,17 +105,7 @@ class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerM "MultiUserCasesBrowserPanel.waitNode.message=Please Wait..." }) void refreshCases() { - if (loadCaseListWorker == null || loadCaseListWorker.isDone()) { - /* - * RJCTODO: Explain this or move the data fetching into the create - * keys method of the nodes... - */ - EmptyNode emptyNode = new EmptyNode(Bundle.MultiUserCasesBrowserPanel_waitNode_message()); - explorerManager.setRootContext(emptyNode); - - loadCaseListWorker = new LoadCaseListWorker(); - loadCaseListWorker.execute(); - } + explorerManager.setRootContext(new MultiUserCasesRootNode()); } /** @@ -162,98 +133,4 @@ class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerM private javax.swing.JScrollPane caseTableScrollPane; // End of variables declaration//GEN-END:variables - /** - * A background task that gets the multi-user case data from the case - * directory lock coordination service nodes. - */ - private class LoadCaseListWorker extends SwingWorker, Void> { - - private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS - private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS - - @Override - protected List doInBackground() throws Exception { - final List cases = new ArrayList<>(); - final CoordinationService coordinationService = CoordinationService.getInstance(); - final List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); - for (String nodeName : nodeList) { - /* - * Ignore case name lock nodes. - */ - final Path nodeNameAsPath = Paths.get(nodeName); - if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) { - continue; - } - - /* - * Ignore case auto ingest log lock nodes and resource lock - * nodes. - */ - final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString(); - if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME) || lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) { - continue; - } - - /* - * Get the data from the case directory lock node. This data may not exist - * for "legacy" nodes. If it is missing, create it. - */ - try { - CaseNodeData nodeData; - byte[] nodeBytes = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, nodeName); - if (nodeBytes != null && nodeBytes.length > 0) { - nodeData = new CaseNodeData(nodeBytes); - if (nodeData.getVersion() > 0) { - cases.add(nodeData); - } else { - nodeData = createNodeDataFromCaseMetadata(nodeName); - } - } else { - nodeData = createNodeDataFromCaseMetadata(nodeName); - } - cases.add(nodeData); - - } catch (CoordinationService.CoordinationServiceException | CaseNodeData.InvalidDataException | IOException | CaseMetadata.CaseMetadataException ex) { - logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex); - } - - } - return cases; - } - - @Override - protected void done() { - try { - final List cases = get(); - EventQueue.invokeLater(() -> { - MultiUserCasesRootNode caseListNode = new MultiUserCasesRootNode(cases); - explorerManager.setRootContext(caseListNode); - }); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, "Unexpected interrupt during background processing", ex); - } catch (ExecutionException ex) { - logger.log(Level.SEVERE, "Error during background processing", ex); - } - } - - private CaseNodeData createNodeDataFromCaseMetadata(String nodeName) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException { - Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS); - File caseDirectory = caseDirectoryPath.toFile(); - if (caseDirectory.exists()) { - File[] files = caseDirectory.listFiles(); - for (File file : files) { - String name = file.getName().toLowerCase(); - if (name.endsWith(CaseMetadata.getFileExtension())) { - CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath())); - CaseNodeData nodeData = new CaseNodeData(metadata); - CoordinationService coordinationService = CoordinationService.getInstance(); - coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray()); - } - } - } - throw new IOException(String.format("Could not find case metadata file for %s", nodeName)); - } - - } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java index 3ed3f15492..c4272927b7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java @@ -19,12 +19,12 @@ package org.sleuthkit.autopsy.casemodule; import java.util.List; +import java.util.logging.Level; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; /** @@ -32,6 +32,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ final class MultiUserCasesRootNode extends AbstractNode { + private static final Logger logger = Logger.getLogger(MultiUserCasesRootNode.class.getName()); + /** * Constructs a root node for displaying MultiUserCaseNodes in a NetBeans * Explorer View. @@ -39,22 +41,19 @@ final class MultiUserCasesRootNode extends AbstractNode { * @param case A list of coordination service node data objects representing * multi-user cases. */ - MultiUserCasesRootNode(List cases) { - super(Children.create(new MultiUserCasesRootNodeChildren(cases), true)); + MultiUserCasesRootNode() { + super(Children.create(new MultiUserCasesRootNodeChildren(), true)); } private static class MultiUserCasesRootNodeChildren extends ChildFactory { - private final List cases; - - MultiUserCasesRootNodeChildren(List cases) { - this.cases = cases; - } - @Override protected boolean createKeys(List keys) { - if (cases != null && cases.size() > 0) { - keys.addAll(cases); + try { + List caseNodeData = MulitUserCaseNodeDataCollector.getNodeData(); + keys.addAll(caseNodeData); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.SEVERE, "Failed to get case node data from coodination service", ex); } return true; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java index 6fdf6044ae..b7039689ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java @@ -25,6 +25,7 @@ import javax.swing.JDialog; import javax.swing.KeyStroke; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Bundle; /** * A singleton JDialog that allows a user to open a multi-user case. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java index 6c533a6ff6..d3e43c736a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java @@ -46,7 +46,7 @@ final class OpenMultiUserCasePanel extends JPanel { */ OpenMultiUserCasePanel(JDialog parentDialog) { this.parentDialog = parentDialog; - initComponents(); + initComponents(); // Machine generated code caseBrowserPanel = new MultiUserCasesBrowserPanel(); caseExplorerScrollPane.add(caseBrowserPanel); caseExplorerScrollPane.setViewportView(caseBrowserPanel); diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java deleted file mode 100755 index f13c2be677..0000000000 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/InvalidNodeDataException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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.coordinationservice; - -/** - * Exception thrown when invlaid coordniation service node data is encountered. - */ -public final class InvalidNodeDataException extends Exception { - - private static final long serialVersionUID = 1L; - - private InvalidNodeDataException(String message) { - super(message); - } - - private InvalidNodeDataException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java deleted file mode 100755 index cfdb5a5d51..0000000000 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/NodeDataUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 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.coordinationservice; - -import java.nio.ByteBuffer; -import javax.lang.model.type.TypeKind; - -/** - * Utilities for reading and writing node data. - */ -public final class NodeDataUtils { - - /** - * This method retrieves a string from a given buffer. Assumes the string - * was written using putStringIntoBuffer and first reads the the length of - * the string. - * - * @param buffer The buffer from which the string will be read. - * - * @return The string read from the buffer. - */ - public static String getStringFromBuffer(ByteBuffer buffer) { - String output = ""; - int length = buffer.getInt(); - if (length > 0) { - byte[] array = new byte[length]; - buffer.get(array, 0, length); - output = new String(array); - } - return output; - } - - /** - * This method puts a given string into a given buffer. The length of the - * string will be inserted prior to the string. - * - * @param stringValue The string to write to the buffer. - * @param buffer The buffer to which the string will be written. - */ - public static void putStringIntoBuffer(String stringValue, ByteBuffer buffer) { - buffer.putInt(stringValue.getBytes().length); - buffer.put(stringValue.getBytes()); - } - - private NodeDataUtils() { - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index ea4f4d3136..f03c7b01cf 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -380,7 +380,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (selectionListener == null) { - this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup + this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); } if (rootNodeChildren != null) { rootNodeChildren.cancelLoadingThumbnails(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index d2a9c40dc0..49945e2383 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.casemodule.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; @@ -1135,9 +1135,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * * @throws CoordinationService.CoordinationServiceException * @throws InterruptedException - * @throws CaseNodeData.InvalidDataException + * @throws IOException */ - private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws CoordinationServiceException, InterruptedException, CaseNodeData.InvalidDataException { + private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws CoordinationServiceException, InterruptedException, IOException { CaseNodeData caseNodeData = new CaseNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString())); caseNodeData.setErrorsOccurred(true); byte[] rawData = caseNodeData.toArray(); @@ -1517,7 +1517,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen job.setErrorsOccurred(true); try { setCaseNodeDataErrorsOccurred(caseDirectoryPath); - } catch (CaseNodeData.InvalidDataException ex) { + } catch (IOException ex) { sysLogger.log(Level.SEVERE, String.format("Error attempting to get case node data for %s", caseDirectoryPath), ex); } } else { @@ -2020,7 +2020,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * for an auto ingest * job. */ - private void processJobs() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeData.InvalidDataException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void processJobs() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeData.InvalidDataException, IOException, JobMetricsCollectionException { sysLogger.log(Level.INFO, "Started processing pending jobs queue"); Lock manifestLock = JobProcessingTask.this.dequeueAndLockNextJob(); while (null != manifestLock) { @@ -2221,7 +2221,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * for an auto ingest * job. */ - private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException { Path manifestPath = currentJob.getManifest().getFilePath(); sysLogger.log(Level.INFO, "Started processing of {0}", manifestPath); currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.PROCESSING); @@ -2309,7 +2309,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * to collect metrics for an * auto ingest job. */ - private void attemptJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void attemptJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException { updateConfiguration(); if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; @@ -2489,7 +2489,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * collect metrics for an auto * ingest job. */ - private void runIngestForJob(Case caseForJob) throws CoordinationServiceException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, JobMetricsCollectionException { + private void runIngestForJob(Case caseForJob) throws CoordinationServiceException, AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, JobMetricsCollectionException { try { if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; @@ -2528,7 +2528,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * collect metrics for an auto * ingest job. */ - private void ingestDataSource(Case caseForJob) throws AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, CoordinationServiceException, JobMetricsCollectionException { + private void ingestDataSource(Case caseForJob) throws AnalysisStartupException, FileExportException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, CoordinationServiceException, JobMetricsCollectionException { if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; } @@ -2585,7 +2585,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * interrupted while blocked, i.e., * if auto ingest is shutting down. */ - private AutoIngestDataSource identifyDataSource() throws AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private AutoIngestDataSource identifyDataSource() throws AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Identifying data source for {0} ", manifestPath); @@ -2619,7 +2619,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * while blocked, i.e., if auto * ingest is shutting down. */ - private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestJobLoggerException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestJobLoggerException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Adding data source for {0} ", manifestPath); @@ -2701,7 +2701,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * while blocked, i.e., if auto * ingest is shutting down. */ - private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) throws AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) throws AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); @@ -2763,7 +2763,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * while blocked, i.e., if auto * ingest is shutting down. */ - private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Starting ingest modules analysis for {0} ", manifestPath); @@ -2901,7 +2901,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * while blocked, i.e., if auto * ingest is shutting down. */ - private void exportFiles(AutoIngestDataSource dataSource) throws FileExportException, AutoIngestJobLoggerException, InterruptedException, CaseNodeData.InvalidDataException, CoordinationServiceException { + private void exportFiles(AutoIngestDataSource dataSource) throws FileExportException, AutoIngestJobLoggerException, InterruptedException, IOException, CoordinationServiceException { Manifest manifest = currentJob.getManifest(); Path manifestPath = manifest.getFilePath(); sysLogger.log(Level.INFO, "Exporting files for {0}", manifestPath); From ad4b17a4119fbd73b15a03caa078783be7d569db Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 28 Jan 2019 16:09:09 -0500 Subject: [PATCH 3/9] Polishing for changes to case, case node data --- .../sleuthkit/autopsy/casemodule/Case.java | 80 ++++++++++++++----- .../autopsy/casemodule/CaseMetadata.java | 2 +- .../autopsy/casemodule/MultiUserCaseNode.java | 2 +- ...va => MultiUserCaseNodeDataCollector.java} | 6 +- .../casemodule/MultiUserCasesRootNode.java | 2 +- 5 files changed, 64 insertions(+), 28 deletions(-) rename Core/src/org/sleuthkit/autopsy/casemodule/{MulitUserCaseNodeDataCollector.java => MultiUserCaseNodeDataCollector.java} (97%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 777f1472ef..35b77f3963 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -40,7 +40,6 @@ 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; @@ -569,7 +568,7 @@ public class Case { * exception. */ @Messages({ - "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.", + "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.", "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case." }) public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException { @@ -577,7 +576,7 @@ public class Case { try { metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex); } if (CaseType.MULTI_USER_CASE == metadata.getCaseType() && !UserPreferences.getIsMultiUserModeEnabled()) { throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings()); @@ -708,7 +707,8 @@ public class Case { "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.", "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service.", - "Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case.",}) + "Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case." + }) public static void deleteCase(CaseMetadata metadata) throws CaseActionException { StopWatch stopWatch = new StopWatch(); stopWatch.start(); @@ -1956,13 +1956,19 @@ public class Case { checkForUserCancellation(); switchLoggingToCaseLogsDirectory(progressIndicator); checkForUserCancellation(); + if (isNewCase) { + saveCaseMetadataToFile(progressIndicator); + } + checkForUserCancellation(); if (isNewCase) { createCaseNodeData(progressIndicator); } else { updateCaseNodeData(progressIndicator); } checkForUserCancellation(); - deleteTempfilesFromCaseDirectory(progressIndicator); + if (!isNewCase) { + deleteTempfilesFromCaseDirectory(progressIndicator); + } checkForUserCancellation(); if (isNewCase) { createCaseDatabase(progressIndicator); @@ -2021,7 +2027,8 @@ public class Case { * for a lower-level exception. */ @Messages({ - "Case.progressMessage.creatingCaseDirectory=Creating case directory...",}) + "Case.progressMessage.creatingCaseDirectory=Creating case directory..." + }) private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); if (new File(metadata.getCaseDirectory()).exists() == false) { @@ -2044,6 +2051,30 @@ public class Case { Logger.setLogDirectory(getLogDirectoryPath()); } + /** + * Saves teh case metadata to a file.SHould not be called until the case + * directory has been created. + * + * @param progressIndicator A progress indicator. + * + * @throws CaseActionException If there is a problem completing the + * operation. The exception will have a + * user-friendly message and may be a wrapper + * for a lower-level exception. + */ + @Messages({ + "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...", + "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}." + }) + private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException { + progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata()); + try { + this.metadata.writeToFile(); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex); + } + } + /** * Creates the node data for the case directory lock coordination service * node. @@ -2060,13 +2091,15 @@ public class Case { "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}." }) private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { - progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData()); - try { - CoordinationService coordinationService = CoordinationService.getInstance(); - CaseNodeData nodeData = new CaseNodeData(metadata); - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex); + if (getCaseType() == CaseType.MULTI_USER_CASE) { + progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData()); + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(metadata); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex); + } } } @@ -2086,14 +2119,16 @@ public class Case { "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}." }) private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { - progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData()); - try { - CoordinationService coordinationService = CoordinationService.getInstance(); - CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); - nodeData.setLastAccessDate(new Date()); - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException | IOException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + if (getCaseType() == CaseType.MULTI_USER_CASE) { + progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData()); + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setLastAccessDate(new Date()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + } } } @@ -2103,7 +2138,8 @@ public class Case { * @param progressIndicator A progress indicator. */ @Messages({ - "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",}) + "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..." + }) private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) { /* * Clear the temp subdirectory of the case directory. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 91c1ca216c..2c0b2c1290 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -363,7 +363,7 @@ public final class CaseMetadata { * @throws CaseMetadataException If there is an error writing to the case * metadata file. */ - private void writeToFile() throws CaseMetadataException { + void writeToFile() throws CaseMetadataException { try { /* * Create the XML DOM. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java index 5d801e2d38..eb69fd58e3 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -66,7 +66,7 @@ final class MultiUserCaseNode extends AbstractNode { setName(caseNodeData.getDisplayName()); setDisplayName(caseNodeData.getDisplayName()); this.caseNodeData = caseNodeData; - this.caseMetadataFilePath = Paths.get(caseNodeData.getDirectory().toString(), caseNodeData.getName() + CaseMetadata.getFileExtension()); + this.caseMetadataFilePath = Paths.get(caseNodeData.getDirectory().toString(), caseNodeData.getDisplayName() + CaseMetadata.getFileExtension()); this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java similarity index 97% rename from Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java rename to Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java index 4ec14d4650..b69e0416cd 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MulitUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java @@ -35,9 +35,9 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Queries the coordination service to collect the multi-user case node data * stored in the case directory lock ZooKeeper nodes. */ -final class MulitUserCaseNodeDataCollector { +final class MultiUserCaseNodeDataCollector { - private static final Logger logger = Logger.getLogger(MulitUserCaseNodeDataCollector.class.getName()); + private static final Logger logger = Logger.getLogger(MultiUserCaseNodeDataCollector.class.getName()); private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS @@ -158,7 +158,7 @@ final class MulitUserCaseNodeDataCollector { /** * Private constructor to prevent instantiation of this utility class. */ - private MulitUserCaseNodeDataCollector() { + private MultiUserCaseNodeDataCollector() { } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java index c4272927b7..1002ba9af9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java @@ -50,7 +50,7 @@ final class MultiUserCasesRootNode extends AbstractNode { @Override protected boolean createKeys(List keys) { try { - List caseNodeData = MulitUserCaseNodeDataCollector.getNodeData(); + List caseNodeData = MultiUserCaseNodeDataCollector.getNodeData(); keys.addAll(caseNodeData); } catch (CoordinationService.CoordinationServiceException ex) { logger.log(Level.SEVERE, "Failed to get case node data from coodination service", ex); From db33091d3707b62f0bbaadfab1f3fdaca1cbe725 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 28 Jan 2019 16:38:02 -0500 Subject: [PATCH 4/9] Move CaseNodeData back to coordination service package --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 1 + .../org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java | 1 + .../autopsy/casemodule/MultiUserCaseNodeDataCollector.java | 1 + .../sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java | 1 + .../{casemodule => coordinationservice}/CaseNodeData.java | 3 ++- .../autopsy/experimental/autoingest/AutoIngestManager.java | 2 +- 6 files changed, 7 insertions(+), 2 deletions(-) rename Core/src/org/sleuthkit/autopsy/{casemodule => coordinationservice}/CaseNodeData.java (98%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 9093fc2f5a..30e05af882 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java index eb69fd58e3..e7f0f70657 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import java.awt.Desktop; import java.awt.event.ActionEvent; import java.io.IOException; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java index b69e0416cd..ac0a077978 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import java.io.File; import java.io.IOException; import java.nio.file.LinkOption; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java index 1002ba9af9..0a15c184e0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import java.util.List; import java.util.logging.Level; import org.openide.nodes.AbstractNode; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/casemodule/CaseNodeData.java rename to Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java index 19b134145e..80336df97b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.coordinationservice; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -28,6 +28,7 @@ import java.nio.file.Paths; import java.text.ParseException; import java.util.Date; import org.sleuthkit.autopsy.casemodule.CaseMetadata; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; /** * An object that converts data for a case directory lock coordination service diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index b110f38107..39b229f856 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.casemodule.CaseNodeData; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; From a44a81227cec28f871fd2e2f7bdc1617cd66149d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 30 Jan 2019 10:15:23 -0500 Subject: [PATCH 5/9] Small fixes for modified open multiuser case feature --- .../sleuthkit/autopsy/casemodule/Case.java | 34 +++++++--- .../autopsy/casemodule/MultiUserCaseNode.java | 67 ++++++++++--------- .../coordinationservice/CaseNodeData.java | 1 - 3 files changed, 59 insertions(+), 43 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 30e05af882..04435c82a3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -569,7 +569,7 @@ public class Case { * exception. */ @Messages({ - "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.", + "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.", "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case." }) public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException { @@ -1741,7 +1741,7 @@ public class Case { } /** - * Updates the case display name. + * Updates the case details. * * @param newDisplayName the new display name for the case * @@ -1757,6 +1757,18 @@ public class Case { } catch (CaseMetadataException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex); } + if (getCaseType() == CaseType.MULTI_USER_CASE) { + if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) { + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setDisplayName(caseDetails.getCaseDisplayName()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + } + } + } if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) { eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber())); } @@ -2108,8 +2120,6 @@ public class Case { * Updates the node data for the case directory lock coordination service * node. * - * @param progressIndicator A progress indicator. - * * @throws CaseActionException If there is a problem completing the * operation. The exception will have a * user-friendly message and may be a wrapper @@ -2122,13 +2132,15 @@ public class Case { private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException { if (getCaseType() == CaseType.MULTI_USER_CASE) { progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData()); - try { - CoordinationService coordinationService = CoordinationService.getInstance(); - CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); - nodeData.setLastAccessDate(new Date()); - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException | IOException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + if (getCaseType() == CaseType.MULTI_USER_CASE) { + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setLastAccessDate(new Date()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); + } } } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java index e7f0f70657..6c3fa7fcd2 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule; import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import java.awt.Desktop; import java.awt.event.ActionEvent; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -34,12 +35,6 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.casemodule.StartupWindowProvider; -import org.sleuthkit.autopsy.casemodule.Bundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.NodeProperty; @@ -50,10 +45,7 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; final class MultiUserCaseNode extends AbstractNode { private static final Logger logger = Logger.getLogger(MultiUserCaseNode.class.getName()); - private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; private final CaseNodeData caseNodeData; - private final Path caseMetadataFilePath; - private final Path caseAutoIngestLogFilePath; /** * Constructs a NetBeans Explorer View node that represents a multi-user @@ -67,8 +59,6 @@ final class MultiUserCaseNode extends AbstractNode { setName(caseNodeData.getDisplayName()); setDisplayName(caseNodeData.getDisplayName()); this.caseNodeData = caseNodeData; - this.caseMetadataFilePath = Paths.get(caseNodeData.getDirectory().toString(), caseNodeData.getDisplayName() + CaseMetadata.getFileExtension()); - this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); } @NbBundle.Messages({ @@ -102,14 +92,14 @@ final class MultiUserCaseNode extends AbstractNode { @Override public Action[] getActions(boolean context) { List actions = new ArrayList<>(); - actions.add(new OpenMultiUserCaseAction(this.caseMetadataFilePath)); - actions.add(new OpenCaseAutoIngestLogAction(this.caseAutoIngestLogFilePath)); + actions.add(new OpenMultiUserCaseAction(this.caseNodeData)); + actions.add(new OpenCaseAutoIngestLogAction(this.caseNodeData)); return actions.toArray(new Action[actions.size()]); } @Override public Action getPreferredAction() { - return new OpenMultiUserCaseAction(this.caseMetadataFilePath); + return new OpenMultiUserCaseAction(this.caseNodeData); } /** @@ -123,30 +113,44 @@ final class MultiUserCaseNode extends AbstractNode { private static final class OpenMultiUserCaseAction extends AbstractAction { private static final long serialVersionUID = 1L; - private final Path caseMetadataFilePath; + private final CaseNodeData caseNodeData; - OpenMultiUserCaseAction(Path caseMetadataFilePath) { + OpenMultiUserCaseAction(CaseNodeData caseNodeData) { super(Bundle.MultiUserNode_OpenMultiUserCaseAction_menuItemText()); - this.caseMetadataFilePath = caseMetadataFilePath; + this.caseNodeData = caseNodeData; } + @Override public void actionPerformed(ActionEvent e) { StartupWindowProvider.getInstance().close(); OpenMultiUserCaseDialog.getInstance().setVisible(false); new Thread(() -> { - try { - Case.openAsCurrentCase(caseMetadataFilePath.toString()); - } catch (CaseActionException ex) { - if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS - /* - * CaseActionException error messages are user-friendly. - */ - MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage())); + String caseMetadataFilePath = null; + File caseDirectory = caseNodeData.getDirectory().toFile(); + File[] filesInDirectory = caseDirectory.listFiles(); + if (filesInDirectory != null) { + for (File file : filesInDirectory) { + if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + caseMetadataFilePath = file.getPath(); + } } + } + if (caseMetadataFilePath != null) { + try { + Case.openAsCurrentCase(caseMetadataFilePath); + } catch (CaseActionException ex) { + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS + } + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage())); + StartupWindowProvider.getInstance().open(); + OpenMultiUserCaseDialog.getInstance().setVisible(true); + }); + } + } else { SwingUtilities.invokeLater(() -> { - StartupWindowProvider.getInstance().open(); - OpenMultiUserCaseDialog.getInstance().setVisible(true); + MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg("Could not locate case metadata file.")); }); } }).start(); @@ -166,16 +170,17 @@ final class MultiUserCaseNode extends AbstractNode { @NbBundle.Messages({ "MultiUserNode.OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File", "MultiUserNode.OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.", - "MultiUserNode.OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.", + "MultiUserNode.OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details." }) private static final class OpenCaseAutoIngestLogAction extends AbstractAction { private static final long serialVersionUID = 1L; + private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; private final Path caseAutoIngestLogFilePath; - OpenCaseAutoIngestLogAction(Path caseAutoIngestLogFilePath) { + OpenCaseAutoIngestLogAction(CaseNodeData caseNodeData) { super(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_menuItemText()); - this.caseAutoIngestLogFilePath = caseAutoIngestLogFilePath; + this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); this.setEnabled(caseAutoIngestLogFilePath.toFile().exists()); } diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java index 80336df97b..209cd27c26 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java @@ -28,7 +28,6 @@ import java.nio.file.Paths; import java.text.ParseException; import java.util.Date; import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; /** * An object that converts data for a case directory lock coordination service From c345713a428ae21f6b5623deebb90c008aae204b Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 30 Jan 2019 17:28:06 -0500 Subject: [PATCH 6/9] Restore open button to multi-user cases panel, polishing --- .../autopsy/casemodule/Bundle.properties | 3 +- .../autopsy/casemodule/MultiUserCaseNode.java | 120 +----------------- .../MultiUserCasesBrowserPanel.java | 20 ++- .../OpenCaseAutoIngestLogAction.java | 83 ++++++++++++ .../casemodule/OpenMultiUserCaseAction.java | 99 +++++++++++++++ .../casemodule/OpenMultiUserCaseDialog.java | 21 ++- .../casemodule/OpenMultiUserCasePanel.form | 29 +++-- .../casemodule/OpenMultiUserCasePanel.java | 111 +++++++++------- 8 files changed, 297 insertions(+), 189 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java create mode 100755 Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index c0fe06e728..e112a6e06f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -235,9 +235,10 @@ ImageFilePanel.md5HashTextField.text= ImageFilePanel.errorLabel.text=Error Label ImageFilePanel.hashValuesNoteLabel.text=NOTE: These values will not be validated when the data source is added. ImageFilePanel.hashValuesLabel.text=Hash Values (optional): -OpenMultiUserCasePanel.bnOpenSingleUserCase.text=Open Single-User Case... # 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. OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name OpenMultiUserCasePanel.cancelButton.text=Cancel +OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case +OpenMultiUserCasePanel.openSingleUserCaseButton.text=Open Single-User Case... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java index 6c3fa7fcd2..aa689b0c25 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNode.java @@ -19,24 +19,13 @@ package org.sleuthkit.autopsy.casemodule; import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; -import java.awt.Desktop; -import java.awt.event.ActionEvent; -import java.io.File; -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.AbstractAction; import javax.swing.Action; -import javax.swing.SwingUtilities; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.NodeProperty; /** @@ -44,7 +33,6 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; */ final class MultiUserCaseNode extends AbstractNode { - private static final Logger logger = Logger.getLogger(MultiUserCaseNode.class.getName()); private final CaseNodeData caseNodeData; /** @@ -103,107 +91,13 @@ final class MultiUserCaseNode extends AbstractNode { } /** - * An action that opens the specified case and hides the multi-user case - * panel. + * Gets the coordintaion service case node data this Explorer View node + * represents. + * + * @return The case node data. */ - @NbBundle.Messages({ - "MultiUserNode.OpenMultiUserCaseAction.menuItemText=Open Case", - "# {0} - caseErrorMessage", "MultiUserNode.OpenMultiUserCaseAction.caseOpeningErrorErrorMsg=Failed to open case: {0}" - }) - private static final class OpenMultiUserCaseAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - private final CaseNodeData caseNodeData; - - OpenMultiUserCaseAction(CaseNodeData caseNodeData) { - super(Bundle.MultiUserNode_OpenMultiUserCaseAction_menuItemText()); - this.caseNodeData = caseNodeData; - } - - @Override - public void actionPerformed(ActionEvent e) { - StartupWindowProvider.getInstance().close(); - OpenMultiUserCaseDialog.getInstance().setVisible(false); - new Thread(() -> { - String caseMetadataFilePath = null; - File caseDirectory = caseNodeData.getDirectory().toFile(); - File[] filesInDirectory = caseDirectory.listFiles(); - if (filesInDirectory != null) { - for (File file : filesInDirectory) { - if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - caseMetadataFilePath = file.getPath(); - } - } - } - if (caseMetadataFilePath != null) { - try { - Case.openAsCurrentCase(caseMetadataFilePath); - } catch (CaseActionException ex) { - if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS - } - SwingUtilities.invokeLater(() -> { - MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage())); - StartupWindowProvider.getInstance().open(); - OpenMultiUserCaseDialog.getInstance().setVisible(true); - }); - } - } else { - SwingUtilities.invokeLater(() -> { - MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg("Could not locate case metadata file.")); - }); - } - }).start(); - } - - @Override - public Object clone() throws CloneNotSupportedException { - super.clone(); - throw new CloneNotSupportedException(); - } - + CaseNodeData getCaseNodeData() { + return this.caseNodeData; } - - /** - * An action that opens the case auto ingest log for the specified case. - */ - @NbBundle.Messages({ - "MultiUserNode.OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File", - "MultiUserNode.OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.", - "MultiUserNode.OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details." - }) - private static final class OpenCaseAutoIngestLogAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; - private final Path caseAutoIngestLogFilePath; - - OpenCaseAutoIngestLogAction(CaseNodeData caseNodeData) { - super(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_menuItemText()); - this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); - this.setEnabled(caseAutoIngestLogFilePath.toFile().exists()); - } - - @Override - public void actionPerformed(ActionEvent e) { - try { - if (caseAutoIngestLogFilePath.toFile().exists()) { - Desktop.getDesktop().edit(caseAutoIngestLogFilePath.toFile()); - } else { - MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_deletedLogErrorMsg()); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error opening case auto ingest log file at %s", caseAutoIngestLogFilePath), ex); //NON-NLS - MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_logOpenFailedErrorMsg()); - } - } - - @Override - public Object clone() throws CloneNotSupportedException { - super.clone(); - throw new CloneNotSupportedException(); - } - - } - + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java index b8522c50d7..620c89a2d6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesBrowserPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.casemodule; import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; @@ -27,7 +28,6 @@ import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; import org.openide.explorer.view.OutlineView; -import org.sleuthkit.autopsy.casemodule.Bundle; /** * A JPanel with a scroll pane child component that contains a NetBeans @@ -52,14 +52,14 @@ final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements Exp outlineView = new org.openide.explorer.view.OutlineView(); initComponents(); outline = outlineView.getOutline(); - customizeOutlineView(); + configureOutlineView(); explorerManager.setRootContext(new MultiUserCasesRootNode()); } /** - * Configures the the table of cases and its columns. + * Configures the child scroll pane component's child OutlineView component. */ - private void customizeOutlineView() { + private void configureOutlineView() { outlineView.setPropertyColumns( Bundle.MultiUserCaseNode_column_createTime(), Bundle.MultiUserCaseNode_column_createTime(), Bundle.MultiUserCaseNode_column_path(), Bundle.MultiUserCaseNode_column_path()); @@ -98,8 +98,16 @@ final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements Exp } /** - * Gets the list of cases known to the review mode cases manager and - * refreshes the cases table. + * Adds a listener to changes in case selection in this browser. + * + * @param listener the ListSelectionListener to add + */ + void addListSelectionListener(ListSelectionListener listener) { + outline.getSelectionModel().addListSelectionListener(listener); + } + + /** + * Refreshes the list of multi-user cases in this browser. */ @NbBundle.Messages({ "MultiUserCasesBrowserPanel.waitNode.message=Please Wait..." diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java new file mode 100755 index 0000000000..dab0b6df73 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java @@ -0,0 +1,83 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Desktop; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * An action that opens a case auto ingest log given the coordination service + * node data for the case. + */ +final class OpenCaseAutoIngestLogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OpenCaseAutoIngestLogAction.class.getName()); + private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt"; + private final Path caseAutoIngestLogFilePath; + + /** + * Constructs an action that opens a case auto ingest log given the + * coordination service node data for the case. + * + * @param caseNodeData The coordination service node data for the case. + */ + @NbBundle.Messages({ + "OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File" + }) + OpenCaseAutoIngestLogAction(CaseNodeData caseNodeData) { + super(Bundle.OpenCaseAutoIngestLogAction_menuItemText()); + this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME); + this.setEnabled(caseAutoIngestLogFilePath.toFile().exists()); + } + + @NbBundle.Messages({ + "OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.", + "OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details." + }) + @Override + public void actionPerformed(ActionEvent e) { + try { + if (caseAutoIngestLogFilePath.toFile().exists()) { + Desktop.getDesktop().edit(caseAutoIngestLogFilePath.toFile()); + } else { + MessageNotifyUtil.Message.error(Bundle.OpenCaseAutoIngestLogAction_deletedLogErrorMsg()); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error opening case auto ingest log file at %s", caseAutoIngestLogFilePath), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.OpenCaseAutoIngestLogAction_logOpenFailedErrorMsg()); + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java new file mode 100755 index 0000000000..d6e29362f3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * An action that opens a multi-user case and hides the open multi-user case + * dialog given the coordination service node data for the case. + */ +final class OpenMultiUserCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OpenMultiUserCaseAction.class.getName()); + private final CaseNodeData caseNodeData; + + /** + * Constructs an action that opens a multi-user case and hides the open + * multi-user case dialog given the coordination service node data for the + * case. + */ + @NbBundle.Messages({ + "OpenMultiUserCaseAction.menuItemText=Open Case" + }) + OpenMultiUserCaseAction(CaseNodeData caseNodeData) { + super(Bundle.OpenMultiUserCaseAction_menuItemText()); + this.caseNodeData = caseNodeData; + } + + @NbBundle.Messages({ + "# {0} - caseErrorMessage", "OpenMultiUserCaseAction.caseOpeningErrorErrorMsg=Failed to open case: {0}" + }) + @Override + public void actionPerformed(ActionEvent e) { + StartupWindowProvider.getInstance().close(); + OpenMultiUserCaseDialog.getInstance().setVisible(false); + new Thread(() -> { + String caseMetadataFilePath = null; + File caseDirectory = caseNodeData.getDirectory().toFile(); + File[] filesInDirectory = caseDirectory.listFiles(); + if (filesInDirectory != null) { + for (File file : filesInDirectory) { + if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { + caseMetadataFilePath = file.getPath(); + } + } + } + if (caseMetadataFilePath != null) { + try { + Case.openAsCurrentCase(caseMetadataFilePath); + } catch (CaseActionException ex) { + if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS + } + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.error(Bundle.OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage())); + StartupWindowProvider.getInstance().open(); + OpenMultiUserCaseDialog.getInstance().setVisible(true); + }); + } + } else { + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.error(Bundle.OpenMultiUserCaseAction_caseOpeningErrorErrorMsg("Could not locate case metadata file.")); + }); + } + }).start(); + } + + @Override + public Object clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java index b7039689ec..58f9a88645 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java @@ -37,8 +37,7 @@ final class OpenMultiUserCaseDialog extends JDialog { private static OpenMultiUserCasePanel multiUserCasesPanel; /** - * Gets the singleton JDialog singleton JDialog that allows a user to open a - * multi-user case. + * Gets the singleton JDialog that allows a user to open a multi-user case. * * @return The singleton JDialog instance. */ @@ -51,16 +50,14 @@ final class OpenMultiUserCaseDialog extends JDialog { } /** - * Constructs a singleton JDialog singleton JDialog that allows a user to - * open a multi-user case. + * Constructs a singleton JDialog that allows a user to open a multi-user + * case. */ @NbBundle.Messages({ "OpenMultiUserCaseDialog.title=Open Multi-User Case" }) private OpenMultiUserCaseDialog() { - super(WindowManager.getDefault().getMainWindow(), - Bundle.OpenMultiUserCaseDialog_title(), - Dialog.ModalityType.APPLICATION_MODAL); + super(WindowManager.getDefault().getMainWindow(), Bundle.OpenMultiUserCaseDialog_title(), Dialog.ModalityType.APPLICATION_MODAL); } /** @@ -72,7 +69,7 @@ final class OpenMultiUserCaseDialog extends JDialog { action -> { setVisible(false); }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); multiUserCasesPanel = new OpenMultiUserCasePanel(this); add(multiUserCasesPanel); @@ -84,14 +81,14 @@ final class OpenMultiUserCaseDialog extends JDialog { * Sets the dialog visibility. When made visible, the dialog refreshes the * display of its OpenMultiUserCasePanel child component. * - * @param value True or false. + * @param makeVisible True or false. */ @Override - public void setVisible(boolean value) { - if (value) { + public void setVisible(boolean makeVisible) { + if (makeVisible) { multiUserCasesPanel.refreshDisplay(); } - super.setVisible(value); + super.setVisible(makeVisible); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form index 8f6f0e138b..30996651a6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.form @@ -28,10 +28,12 @@ - - + + + + - + @@ -45,9 +47,10 @@ - - + + + @@ -55,10 +58,10 @@ - + - + @@ -68,7 +71,7 @@ - + @@ -101,5 +104,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java index d3e43c736a..2576ab4b65 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java @@ -18,14 +18,13 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.util.ArrayList; -import java.util.Date; import javax.swing.JDialog; import javax.swing.JPanel; -import javax.swing.SortOrder; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableRowSorter; +import javax.swing.event.ListSelectionEvent; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; import org.openide.util.Lookup; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; /** * A JPanel that allows a user to open a multi-user case. @@ -50,6 +49,10 @@ final class OpenMultiUserCasePanel extends JPanel { caseBrowserPanel = new MultiUserCasesBrowserPanel(); caseExplorerScrollPane.add(caseBrowserPanel); caseExplorerScrollPane.setViewportView(caseBrowserPanel); + openSelectedCaseButton.setEnabled(false); + caseBrowserPanel.addListSelectionListener((ListSelectionEvent e) -> { + openSelectedCaseButton.setEnabled(caseBrowserPanel.getExplorerManager().getSelectedNodes().length > 0); + }); } /** @@ -60,37 +63,6 @@ final class OpenMultiUserCasePanel extends JPanel { caseBrowserPanel.refreshCases(); } - /** - * A RowSorter which causes columns whose type is Date to be sorted in - * either descending order or ascending order with descending order as the - * default. - */ - private static class RowSorter extends TableRowSorter { - - RowSorter(M tModel) { - super(tModel); - } - - @Override - public void toggleSortOrder(int column) { - if (!this.getModel().getColumnClass(column).equals(Date.class)) { - super.toggleSortOrder(column); //if it isn't a date column perform the regular sorting - } else { - ArrayList sortKeys = new ArrayList<>(getSortKeys()); - if (sortKeys.isEmpty() || sortKeys.get(0).getColumn() != column) { //sort descending - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else if (sortKeys.get(0).getSortOrder() == SortOrder.ASCENDING) { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.ASCENDING)); - } - setSortKeys(sortKeys); - } - } - } - /** * 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 @@ -100,20 +72,21 @@ final class OpenMultiUserCasePanel extends JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - bnOpenSingleUserCase = new javax.swing.JButton(); + openSingleUserCaseButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); searchLabel = new javax.swing.JLabel(); caseExplorerScrollPane = new javax.swing.JScrollPane(); + openSelectedCaseButton = new javax.swing.JButton(); setName("Completed Cases"); // NOI18N setPreferredSize(new java.awt.Dimension(960, 485)); - org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.bnOpenSingleUserCase.text")); // NOI18N - bnOpenSingleUserCase.setMinimumSize(new java.awt.Dimension(156, 23)); - bnOpenSingleUserCase.setPreferredSize(new java.awt.Dimension(156, 23)); - bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(openSingleUserCaseButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.openSingleUserCaseButton.text")); // NOI18N + openSingleUserCaseButton.setMinimumSize(new java.awt.Dimension(156, 23)); + openSingleUserCaseButton.setPreferredSize(new java.awt.Dimension(156, 23)); + openSingleUserCaseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - bnOpenSingleUserCaseActionPerformed(evt); + openSingleUserCaseButtonActionPerformed(evt); } }); @@ -129,6 +102,13 @@ final class OpenMultiUserCasePanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.searchLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(openSelectedCaseButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.openSelectedCaseButton.text")); // NOI18N + openSelectedCaseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + openSelectedCaseButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -139,12 +119,17 @@ final class OpenMultiUserCasePanel extends JPanel { .addComponent(caseExplorerScrollPane) .addGroup(layout.createSequentialGroup() .addComponent(searchLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 350, Short.MAX_VALUE) - .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(32, 32, 32) + .addComponent(openSingleUserCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 172, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 96, Short.MAX_VALUE) + .addComponent(openSelectedCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, openSelectedCaseButton, openSingleUserCaseButton}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -153,10 +138,14 @@ final class OpenMultiUserCasePanel extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(openSingleUserCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(openSelectedCaseButton)) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cancelButton, openSelectedCaseButton, openSingleUserCaseButton}); + }// //GEN-END:initComponents /** @@ -164,20 +153,44 @@ final class OpenMultiUserCasePanel extends JPanel { * * @param evt An ActionEvent, unused. */ - private void bnOpenSingleUserCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenSingleUserCaseActionPerformed + private void openSingleUserCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openSingleUserCaseButtonActionPerformed Lookup.getDefault().lookup(CaseOpenAction.class).openCaseSelectionWindow(); - }//GEN-LAST:event_bnOpenSingleUserCaseActionPerformed + }//GEN-LAST:event_openSingleUserCaseButtonActionPerformed + /** + * Closes the parent open multi-user case dialog. + * + * @param evt An ActionEvent, unused. + */ private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed if (parentDialog != null) { parentDialog.setVisible(false); } }//GEN-LAST:event_cancelButtonActionPerformed + /** + * Opens the multi-user case selected in the child multi-user case browser + * panel. + * + * @param evt An ActionEvent, unused. + */ + private void openSelectedCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openSelectedCaseButtonActionPerformed + ExplorerManager explorerManager = caseBrowserPanel.getExplorerManager(); + Node[] selectedNodes = explorerManager.getSelectedNodes(); + if (selectedNodes.length > 0) { + if (selectedNodes[0] instanceof MultiUserCaseNode) { + MultiUserCaseNode caseNode = (MultiUserCaseNode) selectedNodes[0]; + CaseNodeData nodeData = caseNode.getCaseNodeData(); + new OpenMultiUserCaseAction(nodeData).actionPerformed(evt); + } + } + }//GEN-LAST:event_openSelectedCaseButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton bnOpenSingleUserCase; private javax.swing.JButton cancelButton; private javax.swing.JScrollPane caseExplorerScrollPane; + private javax.swing.JButton openSelectedCaseButton; + private javax.swing.JButton openSingleUserCaseButton; private javax.swing.JLabel searchLabel; // End of variables declaration//GEN-END:variables } From 29df995bf9a4ee891ea38acd8d88512fb828adcb Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 30 Jan 2019 18:30:30 -0500 Subject: [PATCH 7/9] Clean up bundle files --- Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties | 5 ----- .../org/sleuthkit/autopsy/casemodule/Bundle_ja.properties | 2 -- 2 files changed, 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index e112a6e06f..57299ab1c6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -189,8 +189,6 @@ ReviewModeCasePanel.StatusIconHeaderText=Status ReviewModeCasePanel.OutputFolderHeaderText=Output Folder ReviewModeCasePanel.LastAccessedTimeHeaderText=Last Accessed Time ReviewModeCasePanel.MetadataFileHeaderText=Metadata File -OpenMultiUserCasePanel.jLabel1.text=Recent Cases -OpenMultiUserCasePanel.openButton.text=Open CueBannerPanel.newCaseLabel.text=New Case CueBannerPanel.openCaseButton.text= CueBannerPanel.openCaseLabel.text=Open Case @@ -235,9 +233,6 @@ ImageFilePanel.md5HashTextField.text= ImageFilePanel.errorLabel.text=Error Label ImageFilePanel.hashValuesNoteLabel.text=NOTE: These values will not be validated when the data source is added. ImageFilePanel.hashValuesLabel.text=Hash Values (optional): -# 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. OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name OpenMultiUserCasePanel.cancelButton.text=Cancel OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index e20b33d82a..b067b28c98 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -177,8 +177,6 @@ OptionalCasePropertiesPanel.examinerLabel.text=\u8abf\u67fb\u62c5\u5f53\u8005\uf OptionalCasePropertiesPanel.caseDisplayNameLabel.text=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a CueBannerPanel.openRecentCaseLabel.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f CueBannerPanel.openAutoIngestCaseLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f -OpenMultiUserCasePanel.openButton.text=\u958b\u304f -OpenMultiUserCasePanel.jLabel1.text=\u6700\u8fd1\u958b\u3044\u305f\u30d5\u30a1\u30a4\u30eb CueBannerPanel.newCaseLabel.text=\u65b0\u898f\u30b1\u30fc\u30b9\u3092\u4f5c\u6210 CueBannerPanel.openCaseLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f ImageFilePanel.sectorSizeLabel.text=\u30a4\u30f3\u30d7\u30c3\u30c8\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044\uff1a From 4e618f59715eeeed0a210176ec0e7e69c8e5bfaa Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 5 Feb 2019 17:46:31 -0500 Subject: [PATCH 8/9] Polish for multi-user case classes --- .../sleuthkit/autopsy/casemodule/Case.java | 24 +++++++++---------- .../autopsy/casemodule/CaseMetadata.java | 7 +++--- .../MultiUserCaseNodeDataCollector.java | 1 - .../casemodule/MultiUserCasesRootNode.java | 9 +++++-- .../OpenCaseAutoIngestLogAction.java | 4 ++-- .../casemodule/OpenMultiUserCaseAction.java | 4 ++-- .../casemodule/OpenMultiUserCaseDialog.java | 10 -------- .../casemodule/OpenMultiUserCasePanel.java | 12 ++++------ 8 files changed, 31 insertions(+), 40 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 2241cf8a32..c50f83f357 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1589,15 +1589,15 @@ public class Case { public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) { eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId)); } - + /** * Notifies case event subscribers that a data source has been added to the * case database. * * This should not be called from the event dispatch thread (EDT) * - * @param dataSource The data source. - * @param newName The new name for the data source + * @param dataSource The data source. + * @param newName The new name for the data source */ public void notifyDataSourceNameChanged(Content dataSource, String newName) { eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName)); @@ -1776,16 +1776,14 @@ public class Case { } catch (CaseMetadataException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex); } - if (getCaseType() == CaseType.MULTI_USER_CASE) { - if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) { - try { - CoordinationService coordinationService = CoordinationService.getInstance(); - CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); - nodeData.setDisplayName(caseDetails.getCaseDisplayName()); - coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); - } catch (CoordinationServiceException | InterruptedException | IOException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); - } + if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) { + try { + CoordinationService coordinationService = CoordinationService.getInstance(); + CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory())); + nodeData.setDisplayName(caseDetails.getCaseDisplayName()); + coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException | IOException ex) { + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex); } } if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 2c0b2c1290..202ed86f72 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-2017 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import java.nio.file.Paths; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -53,7 +54,7 @@ public final class CaseMetadata { private static final String FILE_EXTENSION = ".aut"; private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss (z)"; - private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING); + private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US); /* * Fields from schema version 1 @@ -127,7 +128,7 @@ public final class CaseMetadata { * @return The date format. */ public static DateFormat getDateFormat() { - return new SimpleDateFormat(DATE_FORMAT_STRING); + return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java index ac0a077978..d90fec1a64 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseNodeDataCollector.java @@ -28,7 +28,6 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java index 0a15c184e0..7a8ec78938 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesRootNode.java @@ -33,8 +33,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ final class MultiUserCasesRootNode extends AbstractNode { - private static final Logger logger = Logger.getLogger(MultiUserCasesRootNode.class.getName()); - + private static final Logger logger = Logger.getLogger(MultiUserCasesRootNode.class.getName()); + /** * Constructs a root node for displaying MultiUserCaseNodes in a NetBeans * Explorer View. @@ -46,6 +46,11 @@ final class MultiUserCasesRootNode extends AbstractNode { super(Children.create(new MultiUserCasesRootNodeChildren(), true)); } + /** + * A child factory for creating child nodes for a MultiUserCasesRootNode. + * The child nodes are of type MultiUserCaseNode. The node keys are of type + * CaseNodeData. + */ private static class MultiUserCasesRootNodeChildren extends ChildFactory { @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java index dab0b6df73..165af8776c 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenCaseAutoIngestLogAction.java @@ -61,7 +61,7 @@ final class OpenCaseAutoIngestLogAction extends AbstractAction { "OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details." }) @Override - public void actionPerformed(ActionEvent e) { + public void actionPerformed(ActionEvent event) { try { if (caseAutoIngestLogFilePath.toFile().exists()) { Desktop.getDesktop().edit(caseAutoIngestLogFilePath.toFile()); @@ -75,7 +75,7 @@ final class OpenCaseAutoIngestLogAction extends AbstractAction { } @Override - public Object clone() throws CloneNotSupportedException { + public OpenCaseAutoIngestLogAction clone() throws CloneNotSupportedException { super.clone(); throw new CloneNotSupportedException(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java index d6e29362f3..5814d6b12b 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseAction.java @@ -55,7 +55,7 @@ final class OpenMultiUserCaseAction extends AbstractAction { "# {0} - caseErrorMessage", "OpenMultiUserCaseAction.caseOpeningErrorErrorMsg=Failed to open case: {0}" }) @Override - public void actionPerformed(ActionEvent e) { + public void actionPerformed(ActionEvent event) { StartupWindowProvider.getInstance().close(); OpenMultiUserCaseDialog.getInstance().setVisible(false); new Thread(() -> { @@ -91,7 +91,7 @@ final class OpenMultiUserCaseAction extends AbstractAction { } @Override - public Object clone() throws CloneNotSupportedException { + public OpenMultiUserCaseAction clone() throws CloneNotSupportedException { super.clone(); throw new CloneNotSupportedException(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java index 58f9a88645..c81ceabd7c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCaseDialog.java @@ -19,13 +19,9 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Dialog; -import java.awt.event.KeyEvent; -import javax.swing.JComponent; import javax.swing.JDialog; -import javax.swing.KeyStroke; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Bundle; /** * A singleton JDialog that allows a user to open a multi-user case. @@ -65,12 +61,6 @@ final class OpenMultiUserCaseDialog extends JDialog { * pressed and adds a OpenMultiUserCasePanel child component. */ private void init() { - getRootPane().registerKeyboardAction( - action -> { - setVisible(false); - }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_IN_FOCUSED_WINDOW); multiUserCasesPanel = new OpenMultiUserCasePanel(this); add(multiUserCasesPanel); pack(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java index 2576ab4b65..5159cb8163 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenMultiUserCasePanel.java @@ -50,7 +50,7 @@ final class OpenMultiUserCasePanel extends JPanel { caseExplorerScrollPane.add(caseBrowserPanel); caseExplorerScrollPane.setViewportView(caseBrowserPanel); openSelectedCaseButton.setEnabled(false); - caseBrowserPanel.addListSelectionListener((ListSelectionEvent e) -> { + caseBrowserPanel.addListSelectionListener((ListSelectionEvent event) -> { openSelectedCaseButton.setEnabled(caseBrowserPanel.getExplorerManager().getSelectedNodes().length > 0); }); } @@ -177,12 +177,10 @@ final class OpenMultiUserCasePanel extends JPanel { private void openSelectedCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openSelectedCaseButtonActionPerformed ExplorerManager explorerManager = caseBrowserPanel.getExplorerManager(); Node[] selectedNodes = explorerManager.getSelectedNodes(); - if (selectedNodes.length > 0) { - if (selectedNodes[0] instanceof MultiUserCaseNode) { - MultiUserCaseNode caseNode = (MultiUserCaseNode) selectedNodes[0]; - CaseNodeData nodeData = caseNode.getCaseNodeData(); - new OpenMultiUserCaseAction(nodeData).actionPerformed(evt); - } + if (selectedNodes.length > 0 && selectedNodes[0] instanceof MultiUserCaseNode) { + MultiUserCaseNode caseNode = (MultiUserCaseNode) selectedNodes[0]; + CaseNodeData nodeData = caseNode.getCaseNodeData(); + new OpenMultiUserCaseAction(nodeData).actionPerformed(evt); } }//GEN-LAST:event_openSelectedCaseButtonActionPerformed From d1cce43396a65e76fd88fa359faa2b4c067b515a Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 5 Feb 2019 17:58:15 -0500 Subject: [PATCH 9/9] Made version field final in CaseNodeData --- .../org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java index 209cd27c26..2fd1b4cf04 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CaseNodeData.java @@ -40,7 +40,7 @@ public final class CaseNodeData { /* * Version 0 fields. */ - private int version; + private final int version; private boolean errorsOccurred; /*