From ef87e9a7b6c41918a8d4b954b95c78097133ba43 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 9 Jan 2018 16:15:08 -0500 Subject: [PATCH 01/14] 3408 initial working changes for multi user case explorer --- .../autopsy/casemodule/Bundle.properties | 10 +- .../autopsy/casemodule/CaseBrowser.form | 47 ++ .../autopsy/casemodule/CaseBrowser.java | 226 +++++++ .../autopsy/casemodule/CaseNode.java | 203 ++++++ .../casemodule/MultiUserCaseManager.java | 442 ------------- .../casemodule/MultiUserCasesPanel.form | 207 +----- .../casemodule/MultiUserCasesPanel.java | 619 ++++++------------ .../datamodel/DisplayableItemNodeVisitor.java | 10 +- 8 files changed, 709 insertions(+), 1055 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseManager.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 1cc822fea5..ab2b5c12dc 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -223,17 +223,11 @@ ReviewModeCasePanel.MetadataFileHeaderText=Metadata File OpenMultiUserCasePanel.jLabel1.text=Recent Cases OpenMultiUserCasePanel.openButton.text=Open OpenMultiUserCasePanel.cancelButton.text=Cancel -MultiUserCasesPanel.rbWeeks.text=Weeks -MultiUserCasesPanel.rbDays.text=Days -MultiUserCasesPanel.bnShowLog.toolTipText=Display case log file for selected case -MultiUserCasesPanel.bnShowLog.text=&Show Auto Ingest Case Log -MultiUserCasesPanel.rbAllCases.text=Everything -MultiUserCasesPanel.bnRefresh.text=&Refresh MultiUserCasesPanel.bnOpen.text=&Open -MultiUserCasesPanel.rbGroupLabel.text=Show cases created in the last 10: -MultiUserCasesPanel.rbMonths.text=Months 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=Start typing to search by case name +MultiUserCasesPanel.cancelButton.text=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form new file mode 100644 index 0000000000..cdb5a13686 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form @@ -0,0 +1,47 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java new file mode 100644 index 0000000000..901f646717 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -0,0 +1,226 @@ +/* + * 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.Component; +import java.awt.Cursor; +import java.lang.reflect.InvocationTargetException; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * A Swing JPanel with a JTabbedPane child component. The tabbed pane contains + * result viewers. + * + * The "main" DataResultPanel for the desktop application has a table viewer + * (DataResultViewerTable) and a thumbnail viewer (DataResultViewerThumbnail), + * plus zero to many additional DataResultViewers, since the DataResultViewer + * interface is an extension point. + * + * The "main" DataResultPanel resides in the "main" results view + * (DataResultTopComponent) that is normally docked into the upper right hand + * side of the main window of the desktop application. + * + * The result viewers in the "main panel" are used to view the child nodes of a + * node selected in the tree view (DirectoryTreeTopComponent) that is normally + * docked into the left hand side of the main window of the desktop application. + * + * Nodes selected in the child results viewers of a DataResultPanel are + * displayed in a content view (implementation of the DataContent interface) + * supplied the panel. The default content view is (DataContentTopComponent) is + * normally docked into the lower right hand side of the main window, underneath + * the results view. A custom content view may be specified instead. + */ +class CaseBrowser extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private Outline outline; + private ExplorerManager em; + private org.openide.explorer.view.OutlineView outlineView; + + /** + * Creates new form 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_status(), Bundle.CaseNode_column_status(), + Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath() + ); + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + outline.setColumnSorted(1, false, 1); //it would be nice if the column index wasn't hardcoded + + } + + /** + * Initializes this panel. Intended to be called by a parent top component + * when the top component is opened. + */ + void open() { + if (null == em) { + /* + * Get an explorer manager to pass to the child result viewers. If + * the application components are put together as expected, this + * will be an explorer manager owned by a parent top component, and + * placed by the top component in the look up that is proxied as the + * action global context when the top component has focus. The + * sharing of this explorer manager enables the same child node + * selections to be made in all of the result viewers. + */ + em = ExplorerManager.find(this); + } + jScrollPane1.setViewportView(outlineView); + setColumnWidths(); + this.setVisible(true); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public void setNode(Node selectedNode) { + + /* + * The quick filter must be reset because when determining column width, + * ETable.getRowCount is called, and the documentation states that quick + * filters must be unset for the method to work "If the quick-filter is + * applied the number of rows do not match the number of rows in the + * model." + */ + outline.unsetQuickFilter(); + // change the cursor to "waiting cursor" for this operation + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + boolean hasChildren = false; + if (selectedNode != null) { + // @@@ This just did a DB round trip to get the count and the results were not saved... + hasChildren = selectedNode.getChildren().getNodesCount() > 0; + } + + if (hasChildren) { + em.setRootContext(selectedNode);; + outline = outlineView.getOutline(); + } else { + Node emptyNode = new AbstractNode(Children.LEAF); + em.setRootContext(emptyNode); // make empty node + outlineView.setPropertyColumns(); // set the empty property header + } + } finally { + this.setCursor(null); + } + } + + public void addListSelectionListener(ListSelectionListener listener) { + outline.getSelectionModel().addListSelectionListener(listener); + } + + String getCasePath() { + int[] selectedRows = outline.getSelectedRows(); + System.out.println("Explored Context: " + em.getExploredContext()); + System.out.println("EM ROOT NODe: " + em.getRootContext().getDisplayName()); + if (selectedRows.length == 1) { + System.out.println("Selected Row: " + selectedRows[0]); + for (int colIndex = 0; colIndex < outline.getColumnCount(); colIndex++) { + TableColumn col = outline.getColumnModel().getColumn(colIndex); + System.out.println("COL: " + col.getHeaderValue().toString()); + if (col.getHeaderValue().toString().equals(Bundle.CaseNode_column_metadataFilePath())) { + try { + return ((NodeProperty)outline.getValueAt(selectedRows[0], colIndex)).getValue().toString(); + } catch (IllegalAccessException ex) { + + } catch (InvocationTargetException ex) { + + } + } + } + + } + return null; + } + + boolean isRowSelected() { + System.out.println("SELECTED ROWS: " + outline.getSelectedRows().length); + return outline.getSelectedRows().length > 0; + } + + private void setColumnWidths() { + int margin = 4; + int padding = 8; + + final int rows = Math.min(100, outline.getRowCount()); + + for (int column = 0; column < outline.getModel().getColumnCount(); column++) { + int columnWidthLimit = 500; + int columnWidth = 0; + + // find the maximum width needed to fit the values for the first 100 rows, at most + for (int row = 0; row < rows; row++) { + TableCellRenderer renderer = outline.getCellRenderer(row, column); + Component comp = outline.prepareRenderer(renderer, row, column); + columnWidth = Math.max(comp.getPreferredSize().width, columnWidth); + } + + columnWidth += 2 * margin + padding; // add margin and regular padding + columnWidth = Math.min(columnWidth, columnWidthLimit); + + outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth); + } + } + + /** + * 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() { + + jScrollPane1 = new javax.swing.JScrollPane(); + + setMinimumSize(new java.awt.Dimension(0, 5)); + setPreferredSize(new java.awt.Dimension(5, 5)); + setLayout(new java.awt.BorderLayout()); + + jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 5)); + jScrollPane1.setOpaque(false); + jScrollPane1.setPreferredSize(new java.awt.Dimension(5, 5)); + add(jScrollPane1, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane jScrollPane1; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java new file mode 100644 index 0000000000..abd6b965d8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java @@ -0,0 +1,203 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.event.ActionEvent; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.swing.AbstractAction; +import javax.swing.Action; +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.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +/** + * Provides a root node for the results views with a single child node that + * displays a message as the sole item in its property sheet, useful for + * displaying explanatory text in the result views when there is a node with no + * children in the tree view. + */ +public final class CaseNode extends AbstractNode { + + @Messages({"CaseNode.column.name=Name", + "CaseNode.column.createdTime=Created Time", + "CaseNode.column.status=Status", + "CaseNode.column.metadataFilePath=Path"}) + + /** + * Provides a root node for the results views with a single child node that + * displays a message as the sole item in its property sheet, useful for + * displaying explanatory text in the result views when there is a node with + * no children in the tree view. + * + * @param displayedMessage The text for the property sheet of the child + * node. + */ + CaseNode(Map caseList) { + super(Children.create(new CaseNodeChildren(caseList), true)); + } + + static class CaseNodeChildren extends ChildFactory> { + + Map caseList; + + public CaseNodeChildren(Map caseList) { + this.caseList = caseList; + } + + @Override + protected boolean createKeys(List> list) { + if (caseList != null && caseList.size() > 0) { + list.addAll(caseList.entrySet()); + } + System.out.println("NUM OF KEYS: " + list.size()); + return true; + } + + @Override + protected Node createNodeForKey(Entry key) { + return new CaseNameNode(key); + } + + } + + /** + * The single child node of an EmptyNode, responsible for displaying a + * message as the sole item in its property sheet. + */ + public static final class CaseNameNode extends DisplayableItemNode { + + CaseMetadata multiUserCase; + String caseMetadataFilePath; + boolean caseHasAlert; + + CaseNameNode(Entry userCase) { + super(Children.LEAF); + multiUserCase = userCase.getKey(); + caseHasAlert = userCase.getValue(); + super.setName(multiUserCase.getCaseDisplayName()); + setName(multiUserCase.getCaseDisplayName()); + setDisplayName(multiUserCase.getCaseDisplayName()); + caseMetadataFilePath = multiUserCase.getFilePath().toString(); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + public String getMetadataFilePath() { + return caseMetadataFilePath; + } + + @Override + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + ss.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), + multiUserCase.getCaseDisplayName())); + ss.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), + multiUserCase.getCreatedDate())); + ss.put(new NodeProperty<>(Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), + (caseHasAlert == true ? "Alert" : ""))); + ss.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), + caseMetadataFilePath)); + return s; + } + + @Override + public Action[] getActions(boolean context) { + List actions = new ArrayList<>(); + actions.addAll(Arrays.asList(super.getActions(context))); + actions.add(new OpenMultiUserCaseAction(multiUserCase.getFilePath())); + return actions.toArray(new Action[actions.size()]); + } + } + + /** + * An action that opens the case node which it was generated off of + */ + private static final class OpenMultiUserCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + private final Path caseMetadataFilePath; + + public OpenMultiUserCaseAction(Path path) { + super("Open Case"); + caseMetadataFilePath = path; + } + + @Override + public void actionPerformed(ActionEvent e) { + StartupWindowProvider.getInstance().close(); + openCaseThread(caseMetadataFilePath); + } + } + + private static void openCaseThread(Path caseMetadataFilePath) { + + 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 + 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 + }); + } + } + ).start(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseManager.java deleted file mode 100644 index bedc09d799..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCaseManager.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2017 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.logging.Level; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; -import org.sleuthkit.autopsy.coreutils.Logger; - -/** - * Handles locating and opening multi-user cases. - */ -final class MultiUserCaseManager { - - private static final Logger LOGGER = Logger.getLogger(MultiUserCaseManager.class.getName()); - private static final String ALERT_FILE_NAME = "autoingest.alert"; - private static MultiUserCaseManager instance; - private CoordinationService coordinationService; - - /** - * Gets the multi-user case manager. - * - * @return The multi-user case manager singleton. - * - * @throws MultiUserCaseManagerException - */ - synchronized static MultiUserCaseManager getInstance() throws MultiUserCaseManager.MultiUserCaseManagerException { - if (null == instance) { - instance = new MultiUserCaseManager(); - } - return instance; - } - - /** - * Constructs an object that handles locating and opening multi-user cases. - * - * @throws MultiUserCaseManagerException - */ - private MultiUserCaseManager() throws MultiUserCaseManagerException { - try { - coordinationService = CoordinationService.getInstance(); - } catch (CoordinationServiceException ex) { - throw new MultiUserCaseManager.MultiUserCaseManagerException("Failed to get the coordination service.", ex); - } - } - - /** - * Gets a list of the cases in the top level case folder - * - * @return List of cases. - * - * @throws CoordinationServiceException - */ - List getCases() throws CoordinationServiceException { - List cases = new ArrayList<>(); - List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES); - for (String node : nodeList) { - Path casePath = Paths.get(node); - File caseFolder = casePath.toFile(); - if (caseFolder.exists()) { - /* - * Search for '*.aut' and 'autoingest.alert' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - continue; - } - String autFilePath = null; - boolean alertFileFound = false; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(".aut")) { - autFilePath = file.getAbsolutePath(); - if (!alertFileFound) { - continue; - } - } - if (!alertFileFound && name.endsWith(ALERT_FILE_NAME)) { - alertFileFound = true; - } - if (autFilePath != null && alertFileFound) { - break; - } - } - - if (autFilePath != null) { - try { - CaseStatus caseStatus; - if (alertFileFound) { - /* - * When an alert file exists, ignore the node data - * and use the ALERT status. - */ - caseStatus = CaseStatus.ALERT; - } else { - byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, node); - if (rawData != null && rawData.length > 0) { - /* - * When node data exists, use the status stored - * in the node data. - */ - CaseNodeData caseNodeData = new CaseNodeData(rawData); - if (caseNodeData.getErrorsOccurred()) { - caseStatus = CaseStatus.ALERT; - } else { - caseStatus = CaseStatus.OK; - } - } else { - /* - * When no node data is available, use the 'OK' - * status to avoid confusing the end-user. - */ - caseStatus = CaseStatus.OK; - } - } - - CaseMetadata caseMetadata = new CaseMetadata(Paths.get(autFilePath)); - cases.add(new MultiUserCase(casePath, caseMetadata, caseStatus)); - } catch (CaseMetadata.CaseMetadataException | MultiUserCase.MultiUserCaseException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } catch (InterruptedException | CaseNodeData.InvalidDataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case node data for '%s'.", node), ex); - } - } - } - } - return cases; - } - - /** - * Opens a multi-user case. - * - * @param caseMetadataFilePath Path to the case metadata file. - * - * @throws CaseActionException - */ - synchronized void openCase(Path caseMetadataFilePath) throws CaseActionException { - /* - * Open the case. - */ - Case.openAsCurrentCase(caseMetadataFilePath.toString()); - } - - /** - * Exception type thrown when there is an error completing a multi-user case - * manager operation. - */ - static final class MultiUserCaseManagerException extends Exception { - - private static final long serialVersionUID = 1L; - - /** - * Constructs an instance of the exception type thrown when there is an - * error completing a multi-user case manager operation. - * - * @param message The exception message. - */ - private MultiUserCaseManagerException(String message) { - super(message); - } - - /** - * Constructs an instance of the exception type thrown when there is an - * error completing a multi-user case manager operation. - * - * @param message The exception message. - * @param cause A Throwable cause for the error. - */ - private MultiUserCaseManagerException(String message, Throwable cause) { - super(message, cause); - } - - } - - /** - * A representation of a multi-user case. - */ - static class MultiUserCase implements Comparable { - - private final Path caseDirectoryPath; - private final String caseDisplayName; - private final String metadataFileName; - private final Date createDate; - private final Date lastAccessedDate; - private CaseStatus status; - - /** - * Constructs a representation of a multi-user case - * - * @param caseDirectoryPath The case directory path. - * @param caseMetadata The case metadata. - * - * @throws MultiUserCaseException If no case metadata (.aut) file is - * found in the case directory. - */ - MultiUserCase(Path caseDirectoryPath, CaseMetadata caseMetadata, CaseStatus status) throws MultiUserCaseException { - this.caseDirectoryPath = caseDirectoryPath; - caseDisplayName = caseMetadata.getCaseDisplayName(); - metadataFileName = caseMetadata.getFilePath().getFileName().toString(); - this.status = status; - BasicFileAttributes fileAttrs = null; - try { - fileAttrs = Files.readAttributes(Paths.get(caseDirectoryPath.toString(), metadataFileName), BasicFileAttributes.class); - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading file attributes of case metadata file in %s, will use current time for case createDate/lastModfiedDate", caseDirectoryPath), ex); - } - if (null != fileAttrs) { - createDate = new Date(fileAttrs.creationTime().toMillis()); - lastAccessedDate = new Date(fileAttrs.lastAccessTime().toMillis()); - } else { - createDate = new Date(); - lastAccessedDate = new Date(); - } - } - - /** - * Gets the case directory path. - * - * @return The case directory path. - */ - Path getCaseDirectoryPath() { - return this.caseDirectoryPath; - } - - /** - * Gets the case display name. This may differ from the name supplied to - * the directory or metadata file names if a case has been renamed. - * - * @return The case display name. - */ - String getCaseDisplayName() { - return this.caseDisplayName; - } - - /** - * Gets the creation date for the case, defined as the create time of - * the case metadata file. - * - * @return The case creation date. - */ - Date getCreationDate() { - return this.createDate; - } - - /** - * Gets the last accessed date for the case, defined as the last - * accessed time of the case metadata file. - * - * @return The last accessed date. - */ - Date getLastAccessedDate() { - return this.lastAccessedDate; - } - - /** - * Gets metadata (.aut) file name. - * - * @return The metadata file name. - */ - String getMetadataFileName() { - return this.metadataFileName; - } - - /** - * Gets the status of this case. - * - * @return See CaseStatus enum definition. - */ - CaseStatus getStatus() { - return status; - } - - /** - * Gets the case metadata from a case directory path. - * - * @param caseDirectoryPath The case directory path. - * - * @return Case metadata. - * - * @throws CaseMetadata.CaseMetadataException If the CaseMetadata object - * cannot be constructed. - * @throws MultiUserCaseException If no case metadata (.aut) - * file is found in the case - * directory. - */ - private CaseMetadata getCaseMetadataFromCaseDirectoryPath(Path caseDirectoryPath) throws CaseMetadata.CaseMetadataException, MultiUserCaseException { - CaseMetadata caseMetadata = null; - - File directory = new File(caseDirectoryPath.toString()); - if (directory.isDirectory()) { - File autFile = null; - - /* - * Attempt to find an AUT file via a directory scan. - */ - for (File file : directory.listFiles()) { - if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) { - autFile = file; - break; - } - } - - if (autFile == null || !autFile.isFile()) { - throw new MultiUserCaseException(String.format("No case metadata (.aut) file found in the case directory '%s'.", caseDirectoryPath.toString())); - } - - caseMetadata = new CaseMetadata(Paths.get(autFile.getAbsolutePath())); - } - - return caseMetadata; - } - - /** - * Indicates whether or not some other object is "equal to" this - * MultiUserCase object. - * - * @param other The other object. - * - * @return True or false. - */ - @Override - public boolean equals(Object other) { - if (!(other instanceof MultiUserCase)) { - return false; - } - if (other == this) { - return true; - } - return this.caseDirectoryPath.toString().equals(((MultiUserCase) other).caseDirectoryPath.toString()); - } - - /** - * Returns a hash code value for this MultiUserCase object. - * - * @return The has code. - */ - @Override - public int hashCode() { - int hash = 7; - hash = 71 * hash + Objects.hashCode(this.caseDirectoryPath); - hash = 71 * hash + Objects.hashCode(this.createDate); - hash = 71 * hash + Objects.hashCode(this.caseDisplayName); - return hash; - } - - /** - * Compares this MultiUserCase object with another MultiUserCase object - * for order. - */ - @Override - public int compareTo(MultiUserCase other) { - return -this.lastAccessedDate.compareTo(other.getLastAccessedDate()); - } - - /** - * Comparator for a descending order sort on date created. - */ - static class LastAccessedDateDescendingComparator implements Comparator { - - /** - * Compares two MultiUserCase objects for order based on last - * accessed date (descending). - * - * @param object The first MultiUserCase object - * @param otherObject The second MultiUserCase object. - * - * @return A negative integer, zero, or a positive integer as the - * first argument is less than, equal to, or greater than - * the second. - */ - @Override - public int compare(MultiUserCase object, MultiUserCase otherObject) { - return -object.getLastAccessedDate().compareTo(otherObject.getLastAccessedDate()); - } - } - - /** - * Exception thrown when there is a problem creating a multi-user case. - */ - final class MultiUserCaseException extends Exception { - - private static final long serialVersionUID = 1L; - - /** - * Constructs an exception to throw when there is a problem creating - * a multi-user case. - * - * @param message The exception message. - */ - private MultiUserCaseException(String message) { - super(message); - } - - /** - * Constructs an exception to throw when there is a problem creating - * a multi-user case. - * - * @param message The exception message. - * @param cause The cause of the exception, if it was an - * exception. - */ - private MultiUserCaseException(String message, Throwable cause) { - super(message, cause); - } - } - - } - - static enum CaseStatus { - OK, - ALERT - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form index 58ef6f79fb..3f897d70c0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form @@ -1,10 +1,6 @@
- - - - @@ -26,30 +22,19 @@ - + - + + - + + - - - - - - - - - - - - - + - @@ -59,26 +44,15 @@ - + - - - - - - - - - - - - - - - - + + + + + - + @@ -95,140 +69,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -239,5 +79,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index e0d63f07d7..e17faab27e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -19,79 +19,47 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Cursor; -import java.awt.Desktop; -import java.io.IOException; +import java.awt.EventQueue; +import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import javax.swing.JDialog; -import javax.swing.JOptionPane; import javax.swing.SortOrder; import javax.swing.SwingWorker; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumn; import javax.swing.table.TableRowSorter; +import org.openide.explorer.ExplorerManager; +import org.openide.explorer.ExplorerUtils; import org.openide.util.Lookup; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.MultiUserCaseManager.MultiUserCase; +import org.openide.windows.TopComponent; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.guiutils.GrayableCellRenderer; -import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; /** * A panel that allows a user to open cases created by auto ingest. */ @NbBundle.Messages({"MultiUSerCasesPanel.caseListLoading.message=Retrieving list of cases, please wait..."}) -final class MultiUserCasesPanel extends javax.swing.JPanel { +final class MultiUserCasesPanel extends TopComponent implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(MultiUserCasesPanel.class.getName()); - private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; - private static final int CASE_COL_MIN_WIDTH = 30; - private static final int CASE_COL_MAX_WIDTH = 2000; - private static final int CASE_COL_PREFERRED_WIDTH = 300; - private static final int TIME_COL_MIN_WIDTH = 40; - private static final int TIME_COL_MAX_WIDTH = 250; - private static final int TIME_COL_PREFERRED_WIDTH = 160; - private static final int STATUS_COL_MIN_WIDTH = 55; - private static final int STATUS_COL_MAX_WIDTH = 250; - private static final int STATUS_COL_PREFERRED_WIDTH = 60; - private static final String CASES_POPULATING_MESSAGE = NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUSerCasesPanel.caseListLoading.message"); - - /* - * The JTable table model for the cases table presented by this view is - * defined by the following string, enum, and array. - * - * TODO (RC): Consider unifying this stuff in an enum as in - * AutoIngestDashboard to make it less error prone. - */ - private static final String CASE_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.CaseHeaderText"); - private static final String CREATEDTIME_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); - private static final String STATUS_ICON_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); - private static final String OUTPUT_FOLDER_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); - private static final String METADATA_FILE_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.MetadataFileHeaderText"); - - enum COLUMN_HEADERS { - - CASE, - CREATEDTIME, - STATUS_ICON, - OUTPUTFOLDER, - METADATA_FILE - } - private final String[] columnNames = {CASE_HEADER, CREATEDTIME_HEADER, STATUS_ICON_HEADER, OUTPUT_FOLDER_HEADER, METADATA_FILE_HEADER}; - private DefaultTableModel caseTableModel; - private JDialog parentDialog; - private LoadTableWorker tableWorker; - private Path currentlySelectedCase; + private final JDialog parentDialog; + private LoadCaseListWorker tableWorker; + private final CaseBrowser caseListPanel; + private final ExplorerManager explorerManager; /** * Constructs a panel that allows a user to open cases created by automated @@ -99,66 +67,23 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { */ MultiUserCasesPanel(JDialog parentDialog) { this.parentDialog = parentDialog; - caseTableModel = new DefaultTableModel(columnNames, 0) { - private static final long serialVersionUID = 1L; - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - @Override - public Class getColumnClass(int col) { - if (this.getColumnName(col).equals(CREATEDTIME_HEADER)) { - return Date.class; - } else { - return super.getColumnClass(col); - } - } - }; - + explorerManager = new ExplorerManager(); + associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); initComponents(); - /* - * Configure the columns of the cases table. - */ - TableColumn theColumn; - theColumn = casesTable.getColumn(CASE_HEADER); - theColumn.setCellRenderer(new GrayableCellRenderer()); - theColumn.setMinWidth(CASE_COL_MIN_WIDTH); - theColumn.setMaxWidth(CASE_COL_MAX_WIDTH); - theColumn.setPreferredWidth(CASE_COL_PREFERRED_WIDTH); - theColumn.setWidth(CASE_COL_PREFERRED_WIDTH); + caseListPanel = new CaseBrowser(); + caseListPanel.open(); - theColumn = casesTable.getColumn(CREATEDTIME_HEADER); - theColumn.setCellRenderer(new LongDateCellRenderer()); - theColumn.setMinWidth(TIME_COL_MIN_WIDTH); - theColumn.setMaxWidth(TIME_COL_MAX_WIDTH); - theColumn.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - theColumn.setWidth(TIME_COL_PREFERRED_WIDTH); - - theColumn = casesTable.getColumn(STATUS_ICON_HEADER); - theColumn.setCellRenderer(new StatusIconCellRenderer()); - theColumn.setMinWidth(STATUS_COL_MIN_WIDTH); - theColumn.setMaxWidth(STATUS_COL_MAX_WIDTH); - theColumn.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); - theColumn.setWidth(STATUS_COL_PREFERRED_WIDTH); - - casesTable.removeColumn(casesTable.getColumn(OUTPUT_FOLDER_HEADER)); - casesTable.removeColumn(casesTable.getColumn(METADATA_FILE_HEADER)); - casesTable.setRowSorter(new RowSorter<>(caseTableModel)); - casesTable.getRowSorter().toggleSortOrder(casesTable.getColumn(CREATEDTIME_HEADER).getModelIndex()); + caseExplorerScrollPane.add(caseListPanel); + caseExplorerScrollPane.setViewportView(caseListPanel); /* * Listen for row selection changes and set button state for the current * selection. */ - casesTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { - //Ignore extra messages. - if (e.getValueIsAdjusting()) { - return; - } + caseListPanel.addListSelectionListener((ListSelectionEvent e) -> { setButtons(); }); + } /** @@ -168,54 +93,12 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { void refresh() { if (tableWorker == null || tableWorker.isDone()) { //create a new TableWorker to and execute it in a background thread if one is not currently working - currentlySelectedCase = getSelectedCase(); //set the table to display text informing the user that the list is being retreived and disable case selection - caseTableModel.setRowCount(0); - casesTable.setRowSelectionAllowed(false); - caseTableModel.addRow(new Object[]{CASES_POPULATING_MESSAGE, null, null, "", ""}); - tableWorker = new LoadTableWorker(); + tableWorker = new LoadCaseListWorker(); tableWorker.execute(); - } - } - /** - * Gets the current selection in the cases table. - * - * @return A path representing the current selected case, null if there is - * no selection. - */ - private Path getSelectedCase() { - try { - int selectedRow = casesTable.getSelectedRow(); - if (selectedRow >= 0 && selectedRow < casesTable.getRowCount()) { - return Paths.get(caseTableModel.getValueAt(casesTable.convertRowIndexToModel(selectedRow), COLUMN_HEADERS.CASE.ordinal()).toString()); - } - } catch (Exception ignored) { - return null; } - return null; - } - /** - * Sets the current selection in the cases table. - * - * @param path The case folder path of the case to select. - */ - private void setSelectedCase(Path path) { - if (path != null) { - try { - for (int row = 0; row < casesTable.getRowCount(); ++row) { - Path temp = Paths.get(caseTableModel.getValueAt(casesTable.convertRowIndexToModel(row), COLUMN_HEADERS.CASE.ordinal()).toString()); - if (temp.compareTo(path) == 0) { // found it - casesTable.setRowSelectionInterval(row, row); - return; - } - } - } catch (Exception ignored) { - casesTable.clearSelection(); - } - } - casesTable.clearSelection(); } /** @@ -223,30 +106,8 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { * in the cases table. */ private void setButtons() { - boolean openEnabled = casesTable.getRowSelectionAllowed() && casesTable.getSelectedRow() >= 0 && casesTable.getSelectedRow() < casesTable.getRowCount(); + boolean openEnabled = caseListPanel.isRowSelected(); bnOpen.setEnabled(openEnabled); - - Path pathToLog = getSelectedCaseLogFilePath(); - boolean showLogEnabled = openEnabled && pathToLog != null && pathToLog.toFile().exists(); - bnShowLog.setEnabled(showLogEnabled); - } - - /** - * Retrieves the log file path for the selected case in the cases table. - * - * @return The case log path. - */ - private Path getSelectedCaseLogFilePath() { - Path retValue = null; - - int selectedRow = casesTable.getSelectedRow(); - int rowCount = casesTable.getRowCount(); - if (selectedRow >= 0 && selectedRow < rowCount) { - String caseDirectory = (String) caseTableModel.getValueAt(casesTable.convertRowIndexToModel(selectedRow), COLUMN_HEADERS.OUTPUTFOLDER.ordinal()); - retValue = Paths.get(caseDirectory, LOG_FILE_NAME); - } - - return retValue; } /** @@ -254,56 +115,40 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { * * @param caseMetadataFilePath The path to the case metadata file. */ - private void openCase(Path caseMetadataFilePath) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + private void openCase(String caseMetadataFilePath) { + if (caseMetadataFilePath != null) { + System.out.println("OPENENING CASE: " + caseMetadataFilePath); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - StartupWindowProvider.getInstance().close(); - if (parentDialog != null) { - parentDialog.setVisible(false); - } - new Thread(() -> { - try { - MultiUserCaseManager.getInstance().openCase(caseMetadataFilePath); - } catch (CaseActionException | MultiUserCaseManager.MultiUserCaseManagerException 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)); - }); + StartupWindowProvider.getInstance().close(); + if (parentDialog != null) { + parentDialog.setVisible(false); } - }).start(); + 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(); + } } - /** - * Indicates whether or not a time satisfies a time filter defined by this - * panel's time filter radio buttons. - * - * @param currentTime The current date and time in milliseconds from the - * Unix epoch. - * @param inputTime The date and time to be tested as milliseconds from - * the Unix epoch. - */ - private boolean passesTimeFilter(long currentTime, long inputTime) { - long numberOfUnits = 10; - long multiplier = 1; - if (rbAllCases.isSelected()) { - return true; - } else if (rbMonths.isSelected()) { - multiplier = 31; - } else if (rbWeeks.isSelected()) { - multiplier = 7; - } else if (rbDays.isSelected()) { - multiplier = 1; - } - return ((currentTime - inputTime) / (1000 * 60 * 60 * 24)) < (numberOfUnits * multiplier); + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; } /** @@ -345,19 +190,11 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - rbGroupHistoryLength = new javax.swing.ButtonGroup(); bnOpen = new javax.swing.JButton(); - scrollPaneTable = new javax.swing.JScrollPane(); - casesTable = new javax.swing.JTable(); - bnRefresh = new javax.swing.JButton(); - panelFilter = new javax.swing.JPanel(); - rbAllCases = new javax.swing.JRadioButton(); - bnShowLog = new javax.swing.JButton(); - rbDays = new javax.swing.JRadioButton(); - rbWeeks = new javax.swing.JRadioButton(); - rbMonths = new javax.swing.JRadioButton(); - rbGroupLabel = new javax.swing.JLabel(); bnOpenSingleUserCase = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + searchLabel = new javax.swing.JLabel(); + caseExplorerScrollPane = new javax.swing.JScrollPane(); setName("Completed Cases"); // NOI18N setPreferredSize(new java.awt.Dimension(960, 485)); @@ -370,84 +207,6 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { } }); - casesTable.setModel(caseTableModel); - casesTable.setRowHeight(20); - casesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - casesTable.addMouseListener(new java.awt.event.MouseAdapter() { - public void mouseClicked(java.awt.event.MouseEvent evt) { - casesTableMouseClicked(evt); - } - }); - scrollPaneTable.setViewportView(casesTable); - - org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnRefresh.text")); // NOI18N - bnRefresh.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - bnRefreshActionPerformed(evt); - } - }); - - rbGroupHistoryLength.add(rbAllCases); - rbAllCases.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(rbAllCases, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.rbAllCases.text")); // NOI18N - rbAllCases.addItemListener(new java.awt.event.ItemListener() { - public void itemStateChanged(java.awt.event.ItemEvent evt) { - rbAllCasesItemStateChanged(evt); - } - }); - - javax.swing.GroupLayout panelFilterLayout = new javax.swing.GroupLayout(panelFilter); - panelFilter.setLayout(panelFilterLayout); - panelFilterLayout.setHorizontalGroup( - panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelFilterLayout.createSequentialGroup() - .addComponent(rbAllCases) - .addGap(0, 0, Short.MAX_VALUE)) - ); - panelFilterLayout.setVerticalGroup( - panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelFilterLayout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(rbAllCases)) - ); - - org.openide.awt.Mnemonics.setLocalizedText(bnShowLog, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnShowLog.text")); // NOI18N - bnShowLog.setToolTipText(org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnShowLog.toolTipText")); // NOI18N - bnShowLog.setEnabled(false); - bnShowLog.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - bnShowLogActionPerformed(evt); - } - }); - - rbGroupHistoryLength.add(rbDays); - org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.rbDays.text")); // NOI18N - rbDays.setName(""); // NOI18N - rbDays.addItemListener(new java.awt.event.ItemListener() { - public void itemStateChanged(java.awt.event.ItemEvent evt) { - rbDaysItemStateChanged(evt); - } - }); - - rbGroupHistoryLength.add(rbWeeks); - org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.rbWeeks.text")); // NOI18N - rbWeeks.addItemListener(new java.awt.event.ItemListener() { - public void itemStateChanged(java.awt.event.ItemEvent evt) { - rbWeeksItemStateChanged(evt); - } - }); - - rbGroupHistoryLength.add(rbMonths); - org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.rbMonths.text")); // NOI18N - rbMonths.addItemListener(new java.awt.event.ItemListener() { - public void itemStateChanged(java.awt.event.ItemEvent evt) { - rbMonthsItemStateChanged(evt); - } - }); - - rbGroupLabel.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.rbGroupLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpenSingleUserCase.text")); // NOI18N bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -455,55 +214,45 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { } }); + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.cancelButton.text")); // NOI18N + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.searchLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(caseExplorerScrollPane) .addGroup(layout.createSequentialGroup() - .addGap(4, 4, 4) + .addComponent(searchLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 555, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 175, Short.MAX_VALUE) .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(bnOpenSingleUserCase) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(bnShowLog) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 13, Short.MAX_VALUE) - .addComponent(rbGroupLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(rbDays) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(rbWeeks) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(rbMonths) - .addGap(0, 0, 0) - .addComponent(panelFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(14, 14, 14) - .addComponent(bnRefresh)) - .addComponent(scrollPaneTable)) + .addComponent(cancelButton))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(6, 6, 6) - .addComponent(scrollPaneTable, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(caseExplorerScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(bnOpen) - .addComponent(bnOpenSingleUserCase) - .addComponent(bnShowLog)) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(rbDays) - .addComponent(rbWeeks) - .addComponent(rbMonths) - .addComponent(rbGroupLabel)) - .addComponent(panelFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(bnRefresh, javax.swing.GroupLayout.Alignment.TRAILING)) - .addGap(0, 0, 0)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton) + .addComponent(bnOpen) + .addComponent(bnOpenSingleUserCase) + .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -513,129 +262,139 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { * @param evt -- The event that caused this to be called */ private void bnOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenActionPerformed - int modelRow = casesTable.convertRowIndexToModel(casesTable.getSelectedRow()); - String caseDirectory = (String) caseTableModel.getValueAt(modelRow, COLUMN_HEADERS.OUTPUTFOLDER.ordinal()); - Path caseMetadataFilePath = Paths.get(caseDirectory, (String) caseTableModel.getValueAt(modelRow, COLUMN_HEADERS.METADATA_FILE.ordinal())); - openCase(caseMetadataFilePath); + openCase(caseListPanel.getCasePath()); }//GEN-LAST:event_bnOpenActionPerformed - /** - * Refresh button action - * - * @param evt -- The event that caused this to be called - */ - private void bnRefreshActionPerformed(java.awt.event.ActionEvent evt) { - refresh(); - } - - private void rbDaysItemStateChanged(java.awt.event.ItemEvent evt) { - if (rbDays.isSelected()) { - refresh(); - } - } - - private void rbAllCasesItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_rbAllCasesItemStateChanged - if (rbAllCases.isSelected()) { - refresh(); - } - }//GEN-LAST:event_rbAllCasesItemStateChanged - - private void rbMonthsItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_rbMonthsItemStateChanged - if (rbMonths.isSelected()) { - refresh(); - } - }//GEN-LAST:event_rbMonthsItemStateChanged - - private void rbWeeksItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_rbWeeksItemStateChanged - if (rbWeeks.isSelected()) { - refresh(); - } - }//GEN-LAST:event_rbWeeksItemStateChanged - - private void bnShowLogActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnShowLogActionPerformed - Path pathToLog = getSelectedCaseLogFilePath(); - if (pathToLog != null) { - try { - if (pathToLog.toFile().exists()) { - Desktop.getDesktop().edit(pathToLog.toFile()); - - } else { - JOptionPane.showMessageDialog(this, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.cannotFindLog"), - org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); - } - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Error attempting to open case auto ingest log file %s", pathToLog), ex); - JOptionPane.showMessageDialog(this, - org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.cannotOpenLog"), - org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.unableToShowLogFile"), - JOptionPane.PLAIN_MESSAGE); - } - } - }//GEN-LAST:event_bnShowLogActionPerformed - - private void casesTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_casesTableMouseClicked - if (evt.getClickCount() == 2 && casesTable.getRowSelectionAllowed() && casesTable.getSelectedRow() >= 0 && casesTable.getSelectedRow() < casesTable.getRowCount()) { - int modelRow = casesTable.convertRowIndexToModel(casesTable.getSelectedRow()); - String caseDirectory = (String) caseTableModel.getValueAt(modelRow, COLUMN_HEADERS.OUTPUTFOLDER.ordinal()); - Path caseMetadataFilePath = Paths.get(caseDirectory, (String) caseTableModel.getValueAt(modelRow, COLUMN_HEADERS.METADATA_FILE.ordinal())); - openCase(caseMetadataFilePath); - } - }//GEN-LAST:event_casesTableMouseClicked - private void bnOpenSingleUserCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenSingleUserCaseActionPerformed Lookup.getDefault().lookup(CaseOpenAction.class).openCaseSelectionWindow(); }//GEN-LAST:event_bnOpenSingleUserCaseActionPerformed + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + if (parentDialog != null) { + parentDialog.setVisible(false); + } + }//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 bnRefresh; - private javax.swing.JButton bnShowLog; - private javax.swing.JTable casesTable; - private javax.swing.JPanel panelFilter; - private javax.swing.JRadioButton rbAllCases; - private javax.swing.JRadioButton rbDays; - private javax.swing.ButtonGroup rbGroupHistoryLength; - private javax.swing.JLabel rbGroupLabel; - private javax.swing.JRadioButton rbMonths; - private javax.swing.JRadioButton rbWeeks; - private javax.swing.JScrollPane scrollPaneTable; + private javax.swing.JButton cancelButton; + private javax.swing.JScrollPane caseExplorerScrollPane; + private javax.swing.JLabel searchLabel; // End of variables declaration//GEN-END:variables - private class LoadTableWorker extends SwingWorker { + private class LoadCaseListWorker extends SwingWorker { - private List cases; + private static final String ALERT_FILE_NAME = "autoingest.alert"; + private Map cases; + + /** + * Gets a list of the cases in the top level case folder + * + * @return List of cases. + * + * @throws CoordinationServiceException + */ + private Map getCases() throws CoordinationService.CoordinationServiceException { + Map cases = new HashMap<>(); + List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); + + for (String node : nodeList) { + Path casePath = Paths.get(node); + File caseFolder = casePath.toFile(); + if (caseFolder.exists()) { + /* + * Search for '*.aut' and 'autoingest.alert' files. + */ + File[] fileArray = caseFolder.listFiles(); + if (fileArray == null) { + continue; + } + String autFilePath = null; + boolean alertFileFound = false; + for (File file : fileArray) { + String name = file.getName().toLowerCase(); + if (autFilePath == null && name.endsWith(".aut")) { + autFilePath = file.getAbsolutePath(); + if (!alertFileFound) { + continue; + } + } + if (!alertFileFound && name.endsWith(ALERT_FILE_NAME)) { + alertFileFound = true; + } + if (autFilePath != null && alertFileFound) { + break; + } + } + + if (autFilePath != null) { + try { + boolean hasAlertStatus = false; + if (alertFileFound) { + /* + * When an alert file exists, ignore the node + * data and use the ALERT status. + */ + hasAlertStatus = true; + } else { + byte[] rawData = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, node); + if (rawData != null && rawData.length > 0) { + /* + * When node data exists, use the status + * stored in the node data. + */ + CaseNodeData caseNodeData = new CaseNodeData(rawData); + if (caseNodeData.getErrorsOccurred()) { + hasAlertStatus = true; + } + } + } + + CaseMetadata caseMetadata = new CaseMetadata(Paths.get(autFilePath)); + cases.put(caseMetadata, hasAlertStatus); + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); + } catch (InterruptedException | CaseNodeData.InvalidDataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case node data for '%s'.", node), ex); + } + } + } + } + return cases; + } @Override protected Void doInBackground() throws Exception { try { - MultiUserCaseManager manager = MultiUserCaseManager.getInstance(); - cases = manager.getCases(); - } catch (MultiUserCaseManager.MultiUserCaseManagerException | CoordinationService.CoordinationServiceException ex) { + 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() { - caseTableModel.setRowCount(0); - long now = new Date().getTime(); - for (MultiUserCase autoIngestCase : cases) { - if (autoIngestCase.getCreationDate() != null && passesTimeFilter(now, autoIngestCase.getCreationDate().getTime())) { - caseTableModel.addRow(new Object[]{ - autoIngestCase.getCaseDisplayName(), - autoIngestCase.getCreationDate(), - (MultiUserCaseManager.CaseStatus.OK != autoIngestCase.getStatus()) ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, - autoIngestCase.getCaseDirectoryPath().toString(), - autoIngestCase.getMetadataFileName()}); + + EventQueue.invokeLater(() -> { + CaseNode caseListNode = new CaseNode(cases); + explorerManager.setRootContext(caseListNode); + String displayName = ""; + Content content = caseListNode.getLookup().lookup(Content.class); + if (content != null) { + try { + displayName = content.getUniquePath(); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: {0}", caseListNode); //NON-NLS + } + } else if (caseListNode.getLookup().lookup(String.class) != null) { + displayName = caseListNode.getLookup().lookup(String.class); } - } - //ensure the cases are able to be selected - casesTable.setRowSelectionAllowed(true); - setSelectedCase(currentlySelectedCase); - setButtons(); + System.out.println("GET CASES DONE"); + setButtons(); + }); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index e6e8afed52..d95c8f099e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.casemodule.CaseNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; @@ -41,7 +42,7 @@ public interface DisplayableItemNodeVisitor { T visit(LocalFileNode dfn); T visit(VirtualDirectoryNode ldn); - + T visit(LocalDirectoryNode ldn); T visit(DirectoryNode dn); @@ -158,6 +159,8 @@ public interface DisplayableItemNodeVisitor { T visit(EmptyNode.MessageNode emptyNode); + T visit(CaseNode.CaseNameNode caseNode); + T visit(InterestingHits.InterestingItemTypeNode aThis); /** @@ -241,7 +244,10 @@ public interface DisplayableItemNodeVisitor { public T visit(EmptyNode.MessageNode ftByMimeTypeEmptyNode) { return defaultVisit(ftByMimeTypeEmptyNode); } - + @Override + public T visit(CaseNode.CaseNameNode caseNameNode) { + return defaultVisit(caseNameNode); + } @Override public T visit(InterestingHits.InterestingItemTypeNode interestingItemTypeNode) { return defaultVisit(interestingItemTypeNode); From 978f37620ba65d4d6652b4e48077ce32872e07e5 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 9 Jan 2018 16:22:37 -0500 Subject: [PATCH 02/14] 3408 remove unused method in CaseBrowser --- .../autopsy/casemodule/CaseBrowser.java | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 901f646717..bbf43746f3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Component; -import java.awt.Cursor; import java.lang.reflect.InvocationTargetException; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; @@ -28,11 +27,6 @@ import javax.swing.table.TableColumn; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.Exceptions; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeProperty; /** @@ -108,39 +102,6 @@ class CaseBrowser extends javax.swing.JPanel { this.setVisible(true); } - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void setNode(Node selectedNode) { - - /* - * The quick filter must be reset because when determining column width, - * ETable.getRowCount is called, and the documentation states that quick - * filters must be unset for the method to work "If the quick-filter is - * applied the number of rows do not match the number of rows in the - * model." - */ - outline.unsetQuickFilter(); - // change the cursor to "waiting cursor" for this operation - this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - boolean hasChildren = false; - if (selectedNode != null) { - // @@@ This just did a DB round trip to get the count and the results were not saved... - hasChildren = selectedNode.getChildren().getNodesCount() > 0; - } - - if (hasChildren) { - em.setRootContext(selectedNode);; - outline = outlineView.getOutline(); - } else { - Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); // make empty node - outlineView.setPropertyColumns(); // set the empty property header - } - } finally { - this.setCursor(null); - } - } - public void addListSelectionListener(ListSelectionListener listener) { outline.getSelectionModel().addListSelectionListener(listener); } From b2915ef4f85ff3108c545d0953de683c8e765194 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 9 Jan 2018 18:50:11 -0500 Subject: [PATCH 03/14] 3408 clean up and bug fixes for MultiUserCasePanel --- .../autopsy/casemodule/CaseBrowser.java | 70 +++++++------- .../autopsy/casemodule/CaseNode.java | 93 ++++++++++--------- .../casemodule/MultiUserCasesPanel.java | 31 ++----- 3 files changed, 96 insertions(+), 98 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index bbf43746f3..3af48d6000 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -23,11 +23,13 @@ import java.lang.reflect.InvocationTargetException; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; +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.explorer.ExplorerManager; -import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.openide.nodes.Node; /** * A Swing JPanel with a JTabbedPane child component. The tabbed pane contains @@ -56,10 +58,10 @@ class CaseBrowser extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private Outline outline; + private final Outline outline; private ExplorerManager em; - private org.openide.explorer.view.OutlineView outlineView; - + private final org.openide.explorer.view.OutlineView outlineView; + private int originalPathColumnIndex = 0; /** * Creates new form CaseBrowser */ @@ -71,13 +73,28 @@ class CaseBrowser extends javax.swing.JPanel { outlineView.setPropertyColumns( Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), - Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath() - ); + Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath()); + customize(); + + } + + private void customize() { + TableColumnModel columnModel = outline.getColumnModel(); + int dateColumnIndex = 0; + for (int index = 0; index < columnModel.getColumnCount(); index++) { //get indexes for hidden column and default sorting 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; + } + } + ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex); + ((ETableColumnModel) columnModel).setColumnHidden(column, true); outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - outline.setColumnSorted(1, false, 1); //it would be nice if the column index wasn't hardcoded - + outline.setColumnSorted(dateColumnIndex, false, 1); //it would be nice if the column index wasn't hardcoded } /** @@ -102,37 +119,28 @@ class CaseBrowser extends javax.swing.JPanel { this.setVisible(true); } - public void addListSelectionListener(ListSelectionListener listener) { + void setRowSelectionAllowed(boolean allowed) { + outline.setRowSelectionAllowed(allowed); + } + + void addListSelectionListener(ListSelectionListener listener) { outline.getSelectionModel().addListSelectionListener(listener); } String getCasePath() { int[] selectedRows = outline.getSelectedRows(); - System.out.println("Explored Context: " + em.getExploredContext()); - System.out.println("EM ROOT NODe: " + em.getRootContext().getDisplayName()); if (selectedRows.length == 1) { - System.out.println("Selected Row: " + selectedRows[0]); - for (int colIndex = 0; colIndex < outline.getColumnCount(); colIndex++) { - TableColumn col = outline.getColumnModel().getColumn(colIndex); - System.out.println("COL: " + col.getHeaderValue().toString()); - if (col.getHeaderValue().toString().equals(Bundle.CaseNode_column_metadataFilePath())) { - try { - return ((NodeProperty)outline.getValueAt(selectedRows[0], colIndex)).getValue().toString(); - } catch (IllegalAccessException ex) { - - } catch (InvocationTargetException ex) { - - } - } + try { + return ((Node.Property) outline.getModel().getValueAt(outline.convertRowIndexToModel(selectedRows[0]), originalPathColumnIndex)).getValue().toString(); + } catch (IllegalAccessException | InvocationTargetException ex) { + System.out.println("THROW"); } - } return null; } - + boolean isRowSelected() { - System.out.println("SELECTED ROWS: " + outline.getSelectedRows().length); - return outline.getSelectedRows().length > 0; + return outline.getRowSelectionAllowed() && outline.getSelectedRows().length > 0; } private void setColumnWidths() { @@ -141,8 +149,8 @@ class CaseBrowser extends javax.swing.JPanel { final int rows = Math.min(100, outline.getRowCount()); - for (int column = 0; column < outline.getModel().getColumnCount(); column++) { - int columnWidthLimit = 500; + for (int column = 0; column < outline.getColumnModel().getColumnCount(); column++) { + int columnWidthLimit = 800; int columnWidth = 0; // find the maximum width needed to fit the values for the first 100 rows, at most diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java index abd6b965d8..bde2cb0b73 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.event.ActionEvent; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -38,6 +37,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; + /** * Provides a root node for the results views with a single child node that * displays a message as the sole item in its property sheet, useful for @@ -66,18 +66,17 @@ public final class CaseNode extends AbstractNode { static class CaseNodeChildren extends ChildFactory> { - Map caseList; + private final Map caseMap; - public CaseNodeChildren(Map caseList) { - this.caseList = caseList; + CaseNodeChildren(Map caseMap) { + this.caseMap = caseMap; } @Override protected boolean createKeys(List> list) { - if (caseList != null && caseList.size() > 0) { - list.addAll(caseList.entrySet()); + if (caseMap != null && caseMap.size() > 0) { + list.addAll(caseMap.entrySet()); } - System.out.println("NUM OF KEYS: " + list.size()); return true; } @@ -94,18 +93,20 @@ public final class CaseNode extends AbstractNode { */ public static final class CaseNameNode extends DisplayableItemNode { - CaseMetadata multiUserCase; - String caseMetadataFilePath; - boolean caseHasAlert; + private final String caseName; + private final String caseCreatedDate; + private final String caseMetadataFilePath; + private final boolean caseHasAlert; CaseNameNode(Entry userCase) { super(Children.LEAF); - multiUserCase = userCase.getKey(); + caseName = userCase.getKey().getCaseDisplayName(); + caseCreatedDate = userCase.getKey().getCreatedDate(); caseHasAlert = userCase.getValue(); - super.setName(multiUserCase.getCaseDisplayName()); - setName(multiUserCase.getCaseDisplayName()); - setDisplayName(multiUserCase.getCaseDisplayName()); - caseMetadataFilePath = multiUserCase.getFilePath().toString(); + super.setName(caseName); + setName(caseName); + setDisplayName(caseName); + caseMetadataFilePath = userCase.getKey().getFilePath().toString(); } @Override @@ -122,7 +123,7 @@ public final class CaseNode extends AbstractNode { public String getItemType() { return getClass().getName(); } - + public String getMetadataFilePath() { return caseMetadataFilePath; } @@ -136,9 +137,9 @@ public final class CaseNode extends AbstractNode { s.put(ss); } ss.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), - multiUserCase.getCaseDisplayName())); + caseName)); ss.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), - multiUserCase.getCreatedDate())); + caseCreatedDate)); ss.put(new NodeProperty<>(Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), (caseHasAlert == true ? "Alert" : ""))); ss.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), @@ -150,7 +151,7 @@ public final class CaseNode extends AbstractNode { public Action[] getActions(boolean context) { List actions = new ArrayList<>(); actions.addAll(Arrays.asList(super.getActions(context))); - actions.add(new OpenMultiUserCaseAction(multiUserCase.getFilePath())); + actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); return actions.toArray(new Action[actions.size()]); } } @@ -162,9 +163,9 @@ public final class CaseNode extends AbstractNode { private static final long serialVersionUID = 1L; - private final Path caseMetadataFilePath; + private final String caseMetadataFilePath; - public OpenMultiUserCaseAction(Path path) { + OpenMultiUserCaseAction(String path) { super("Open Case"); caseMetadataFilePath = path; } @@ -172,32 +173,32 @@ public final class CaseNode extends AbstractNode { @Override public void actionPerformed(ActionEvent e) { StartupWindowProvider.getInstance().close(); - openCaseThread(caseMetadataFilePath); + 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 + }); + } + } + ).start(); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. } } - private static void openCaseThread(Path caseMetadataFilePath) { - - 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 - 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 - }); - } - } - ).start(); - } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index e17faab27e..b4e16bfa91 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -45,13 +45,12 @@ import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.datamodel.EmptyNode; /** * A panel that allows a user to open cases created by auto ingest. */ -@NbBundle.Messages({"MultiUSerCasesPanel.caseListLoading.message=Retrieving list of cases, please wait..."}) +@NbBundle.Messages({"MultiUserCasesPanel.caseListLoading.message=Please wait..."}) final class MultiUserCasesPanel extends TopComponent implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; @@ -73,7 +72,7 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. caseListPanel = new CaseBrowser(); caseListPanel.open(); - + caseListPanel.setRowSelectionAllowed(false); caseExplorerScrollPane.add(caseListPanel); caseExplorerScrollPane.setViewportView(caseListPanel); /* @@ -92,11 +91,13 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. */ void refresh() { if (tableWorker == null || tableWorker.isDone()) { + caseListPanel.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.MultiUserCasesPanel_caseListLoading_message()); + explorerManager.setRootContext(emptyNode); tableWorker = new LoadCaseListWorker(); tableWorker.execute(); - } } @@ -117,7 +118,6 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. */ private void openCase(String caseMetadataFilePath) { if (caseMetadataFilePath != null) { - System.out.println("OPENENING CASE: " + caseMetadataFilePath); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); StartupWindowProvider.getInstance().close(); @@ -296,7 +296,7 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. * @throws CoordinationServiceException */ private Map getCases() throws CoordinationService.CoordinationServiceException { - Map cases = new HashMap<>(); + Map casesMap = new HashMap<>(); List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); for (String node : nodeList) { @@ -352,7 +352,7 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. } CaseMetadata caseMetadata = new CaseMetadata(Paths.get(autFilePath)); - cases.put(caseMetadata, hasAlertStatus); + casesMap.put(caseMetadata, hasAlertStatus); } catch (CaseMetadata.CaseMetadataException ex) { LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); } catch (InterruptedException | CaseNodeData.InvalidDataException ex) { @@ -361,7 +361,7 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. } } } - return cases; + return casesMap; } @Override @@ -381,18 +381,7 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. EventQueue.invokeLater(() -> { CaseNode caseListNode = new CaseNode(cases); explorerManager.setRootContext(caseListNode); - String displayName = ""; - Content content = caseListNode.getLookup().lookup(Content.class); - if (content != null) { - try { - displayName = content.getUniquePath(); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: {0}", caseListNode); //NON-NLS - } - } else if (caseListNode.getLookup().lookup(String.class) != null) { - displayName = caseListNode.getLookup().lookup(String.class); - } - System.out.println("GET CASES DONE"); + caseListPanel.setRowSelectionAllowed(true); setButtons(); }); } From 96d9bdb0229fbf30ec30b50c2e29ed1ecfae34d2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 10:55:09 -0500 Subject: [PATCH 04/14] 3408 adjust context menu options for open multi user case panel --- Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java index bde2cb0b73..87862d4d01 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java @@ -20,10 +20,10 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.event.ActionEvent; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.SwingUtilities; @@ -33,6 +33,7 @@ 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.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -50,6 +51,7 @@ public final class CaseNode extends AbstractNode { "CaseNode.column.createdTime=Created Time", "CaseNode.column.status=Status", "CaseNode.column.metadataFilePath=Path"}) + private static final Logger LOGGER = Logger.getLogger(CaseNode.class.getName()); /** * Provides a root node for the results views with a single child node that @@ -150,7 +152,7 @@ public final class CaseNode extends AbstractNode { @Override public Action[] getActions(boolean context) { List actions = new ArrayList<>(); - actions.addAll(Arrays.asList(super.getActions(context))); + // actions.addAll(Arrays.asList(super.getActions(context))); actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); return actions.toArray(new Action[actions.size()]); } @@ -173,13 +175,14 @@ public final class CaseNode extends AbstractNode { @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 + 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(() -> { From 84d113f965863246080db7bc937c76cae512b7d2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 11:36:05 -0500 Subject: [PATCH 05/14] 3408 move explorer provider manager down to CaseBrowser level and eliminate TopComponent --- .../autopsy/casemodule/CaseBrowser.java | 154 ++++++++++++++++-- .../casemodule/MultiUserCasesPanel.java | 144 +--------------- 2 files changed, 146 insertions(+), 152 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 3af48d6000..1eaef654eb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -28,8 +28,21 @@ 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.explorer.ExplorerManager; import org.openide.nodes.Node; +import java.awt.EventQueue; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.openide.explorer.ExplorerManager; +import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.EmptyNode; /** * A Swing JPanel with a JTabbedPane child component. The tabbed pane contains @@ -54,7 +67,7 @@ import org.openide.nodes.Node; * normally docked into the lower right hand side of the main window, underneath * the results view. A custom content view may be specified instead. */ -class CaseBrowser extends javax.swing.JPanel { +class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; @@ -62,6 +75,14 @@ class CaseBrowser extends javax.swing.JPanel { 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 new form CaseBrowser */ @@ -103,16 +124,7 @@ class CaseBrowser extends javax.swing.JPanel { */ void open() { if (null == em) { - /* - * Get an explorer manager to pass to the child result viewers. If - * the application components are put together as expected, this - * will be an explorer manager owned by a parent top component, and - * placed by the top component in the look up that is proxied as the - * action global context when the top component has focus. The - * sharing of this explorer manager enables the same child node - * selections to be made in all of the result viewers. - */ - em = ExplorerManager.find(this); + em = new ExplorerManager(); } jScrollPane1.setViewportView(outlineView); setColumnWidths(); @@ -167,6 +179,23 @@ class CaseBrowser extends javax.swing.JPanel { } } + /** + * Gets the list of cases known to the review mode cases manager and + * refreshes the cases table. + */ + void refresh() { + if (tableWorker == null || tableWorker.isDone()) { + 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.MultiUserCasesPanel_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 @@ -191,5 +220,106 @@ class CaseBrowser extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JScrollPane jScrollPane1; // End of variables declaration//GEN-END:variables + private class LoadCaseListWorker extends SwingWorker { + private static final String ALERT_FILE_NAME = "autoingest.alert"; + private Map cases; + + /** + * Gets a list of the cases in the top level case folder + * + * @return List of cases. + * + * @throws CoordinationServiceException + */ + private Map getCases() throws CoordinationService.CoordinationServiceException { + Map casesMap = new HashMap<>(); + List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); + + for (String node : nodeList) { + Path casePath = Paths.get(node); + File caseFolder = casePath.toFile(); + if (caseFolder.exists()) { + /* + * Search for '*.aut' and 'autoingest.alert' files. + */ + File[] fileArray = caseFolder.listFiles(); + if (fileArray == null) { + continue; + } + String autFilePath = null; + boolean alertFileFound = false; + for (File file : fileArray) { + String name = file.getName().toLowerCase(); + if (autFilePath == null && name.endsWith(".aut")) { + autFilePath = file.getAbsolutePath(); + if (!alertFileFound) { + continue; + } + } + if (!alertFileFound && name.endsWith(ALERT_FILE_NAME)) { + alertFileFound = true; + } + if (autFilePath != null && alertFileFound) { + break; + } + } + + if (autFilePath != null) { + try { + boolean hasAlertStatus = false; + if (alertFileFound) { + /* + * When an alert file exists, ignore the node + * data and use the ALERT status. + */ + hasAlertStatus = true; + } else { + byte[] rawData = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, node); + if (rawData != null && rawData.length > 0) { + /* + * When node data exists, use the status + * stored in the node data. + */ + CaseNodeData caseNodeData = new CaseNodeData(rawData); + if (caseNodeData.getErrorsOccurred()) { + hasAlertStatus = true; + } + } + } + + CaseMetadata caseMetadata = new CaseMetadata(Paths.get(autFilePath)); + casesMap.put(caseMetadata, hasAlertStatus); + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); + } catch (InterruptedException | CaseNodeData.InvalidDataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case node data for '%s'.", node), ex); + } + } + } + } + return casesMap; + } + + @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(() -> { + CaseNode caseListNode = new CaseNode(cases); + em.setRootContext(caseListNode); + setRowSelectionAllowed(true); + }); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index b4e16bfa91..d012107e1f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -19,46 +19,30 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Cursor; -import java.awt.EventQueue; -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.logging.Level; import javax.swing.JDialog; +import javax.swing.JPanel; import javax.swing.SortOrder; -import javax.swing.SwingWorker; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableRowSorter; -import org.openide.explorer.ExplorerManager; -import org.openide.explorer.ExplorerUtils; import org.openide.util.Lookup; import org.openide.util.NbBundle; -import org.openide.windows.TopComponent; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.EmptyNode; /** * A panel that allows a user to open cases created by auto ingest. */ @NbBundle.Messages({"MultiUserCasesPanel.caseListLoading.message=Please wait..."}) -final class MultiUserCasesPanel extends TopComponent implements ExplorerManager.Provider { +final class MultiUserCasesPanel extends JPanel{ - private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(MultiUserCasesPanel.class.getName()); private final JDialog parentDialog; - private LoadCaseListWorker tableWorker; private final CaseBrowser caseListPanel; - private final ExplorerManager explorerManager; /** * Constructs a panel that allows a user to open cases created by automated @@ -66,8 +50,6 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. */ MultiUserCasesPanel(JDialog parentDialog) { this.parentDialog = parentDialog; - explorerManager = new ExplorerManager(); - associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); initComponents(); caseListPanel = new CaseBrowser(); @@ -90,23 +72,14 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. * refreshes the cases table. */ void refresh() { - if (tableWorker == null || tableWorker.isDone()) { - caseListPanel.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.MultiUserCasesPanel_caseListLoading_message()); - explorerManager.setRootContext(emptyNode); - tableWorker = new LoadCaseListWorker(); - tableWorker.execute(); - } - + caseListPanel.refresh(); } /** * Enables/disables the Open and Show Log buttons based on the case selected * in the cases table. */ - private void setButtons() { + void setButtons() { boolean openEnabled = caseListPanel.isRowSelected(); bnOpen.setEnabled(openEnabled); } @@ -146,11 +119,6 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. } } - @Override - public ExplorerManager getExplorerManager() { - return explorerManager; - } - /** * RowSorter which makes columns whose type is Date to be sorted first in * Descending order then in Ascending order @@ -282,108 +250,4 @@ final class MultiUserCasesPanel extends TopComponent implements ExplorerManager. private javax.swing.JScrollPane caseExplorerScrollPane; private javax.swing.JLabel searchLabel; // End of variables declaration//GEN-END:variables - - private class LoadCaseListWorker extends SwingWorker { - - private static final String ALERT_FILE_NAME = "autoingest.alert"; - private Map cases; - - /** - * Gets a list of the cases in the top level case folder - * - * @return List of cases. - * - * @throws CoordinationServiceException - */ - private Map getCases() throws CoordinationService.CoordinationServiceException { - Map casesMap = new HashMap<>(); - List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); - - for (String node : nodeList) { - Path casePath = Paths.get(node); - File caseFolder = casePath.toFile(); - if (caseFolder.exists()) { - /* - * Search for '*.aut' and 'autoingest.alert' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - continue; - } - String autFilePath = null; - boolean alertFileFound = false; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(".aut")) { - autFilePath = file.getAbsolutePath(); - if (!alertFileFound) { - continue; - } - } - if (!alertFileFound && name.endsWith(ALERT_FILE_NAME)) { - alertFileFound = true; - } - if (autFilePath != null && alertFileFound) { - break; - } - } - - if (autFilePath != null) { - try { - boolean hasAlertStatus = false; - if (alertFileFound) { - /* - * When an alert file exists, ignore the node - * data and use the ALERT status. - */ - hasAlertStatus = true; - } else { - byte[] rawData = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, node); - if (rawData != null && rawData.length > 0) { - /* - * When node data exists, use the status - * stored in the node data. - */ - CaseNodeData caseNodeData = new CaseNodeData(rawData); - if (caseNodeData.getErrorsOccurred()) { - hasAlertStatus = true; - } - } - } - - CaseMetadata caseMetadata = new CaseMetadata(Paths.get(autFilePath)); - casesMap.put(caseMetadata, hasAlertStatus); - } catch (CaseMetadata.CaseMetadataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } catch (InterruptedException | CaseNodeData.InvalidDataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case node data for '%s'.", node), ex); - } - } - } - } - return casesMap; - } - - @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(() -> { - CaseNode caseListNode = new CaseNode(cases); - explorerManager.setRootContext(caseListNode); - caseListPanel.setRowSelectionAllowed(true); - setButtons(); - }); - } - } } From f3bdf7d84e455b431e4f8359f4a1806786d5ef6a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 11:40:40 -0500 Subject: [PATCH 06/14] 3408 fix capilization and inline enabling of button --- .../sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index d012107e1f..5eaa5bed6d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * A panel that allows a user to open cases created by auto ingest. */ -@NbBundle.Messages({"MultiUserCasesPanel.caseListLoading.message=Please wait..."}) +@NbBundle.Messages({"MultiUserCasesPanel.caseListLoading.message=Please Wait..."}) final class MultiUserCasesPanel extends JPanel{ private static final Logger LOGGER = Logger.getLogger(MultiUserCasesPanel.class.getName()); @@ -80,8 +80,7 @@ final class MultiUserCasesPanel extends JPanel{ * in the cases table. */ void setButtons() { - boolean openEnabled = caseListPanel.isRowSelected(); - bnOpen.setEnabled(openEnabled); + bnOpen.setEnabled(caseListPanel.isRowSelected()); } /** From 80b77a0739f19ae577d1638c390ad994b4a6a06b Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 12:29:59 -0500 Subject: [PATCH 07/14] 3408 clean up comments and names for multi user case explorer changes --- .../autopsy/casemodule/{CaseNode.java => MultiUserNode.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Core/src/org/sleuthkit/autopsy/casemodule/{CaseNode.java => MultiUserNode.java} (100%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java similarity index 100% rename from Core/src/org/sleuthkit/autopsy/casemodule/CaseNode.java rename to Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java From 9d4f6bb146c53cb226bd025a48fcfb121e801c1c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 12:30:07 -0500 Subject: [PATCH 08/14] 3408 clean up comments and names for multi user case explorer changes --- .../autopsy/casemodule/CaseBrowser.form | 2 +- .../autopsy/casemodule/CaseBrowser.java | 96 +++++++++---------- .../casemodule/MultiUserCasesPanel.java | 21 ++-- .../autopsy/casemodule/MultiUserNode.java | 56 +++++------ .../datamodel/DisplayableItemNodeVisitor.java | 6 +- 5 files changed, 81 insertions(+), 100 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form index cdb5a13686..edda7a749c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.form @@ -24,7 +24,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 1eaef654eb..4c7980fe32 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -39,44 +39,29 @@ import java.util.Map; 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; /** - * A Swing JPanel with a JTabbedPane child component. The tabbed pane contains - * result viewers. + * A Swing JPanel with a scroll pane child component. The scroll pane contain + * the table of cases. * - * The "main" DataResultPanel for the desktop application has a table viewer - * (DataResultViewerTable) and a thumbnail viewer (DataResultViewerThumbnail), - * plus zero to many additional DataResultViewers, since the DataResultViewer - * interface is an extension point. + * Used to display a list of multi user cases and allow the user to open one of + * them. * - * The "main" DataResultPanel resides in the "main" results view - * (DataResultTopComponent) that is normally docked into the upper right hand - * side of the main window of the desktop application. - * - * The result viewers in the "main panel" are used to view the child nodes of a - * node selected in the tree view (DirectoryTreeTopComponent) that is normally - * docked into the left hand side of the main window of the desktop application. - * - * Nodes selected in the child results viewers of a DataResultPanel are - * displayed in a content view (implementation of the DataContent interface) - * supplied the panel. The default content view is (DataContentTopComponent) is - * normally docked into the lower right hand side of the main window, underneath - * the results view. A custom content view may be specified instead. */ 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; + private LoadCaseMapWorker tableWorker; @Override public ExplorerManager getExplorerManager() { @@ -84,7 +69,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider } /** - * Creates new form CaseBrowser + * Creates a new CaseBrowser */ CaseBrowser() { outlineView = new org.openide.explorer.view.OutlineView(); @@ -99,6 +84,9 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider } + /** + * Configures the the table of cases and its columns. + */ private void customize() { TableColumnModel columnModel = outline.getColumnModel(); int dateColumnIndex = 0; @@ -116,25 +104,20 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); outline.setColumnSorted(dateColumnIndex, false, 1); //it would be nice if the column index wasn't hardcoded - } - - /** - * Initializes this panel. Intended to be called by a parent top component - * when the top component is opened. - */ - void open() { if (null == em) { em = new ExplorerManager(); } - jScrollPane1.setViewportView(outlineView); + caseTableScrollPane.setViewportView(outlineView); setColumnWidths(); this.setVisible(true); + outline.setRowSelectionAllowed(false); } - void setRowSelectionAllowed(boolean allowed) { - outline.setRowSelectionAllowed(allowed); - } - + /** + * 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); } @@ -145,12 +128,17 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider try { return ((Node.Property) outline.getModel().getValueAt(outline.convertRowIndexToModel(selectedRows[0]), originalPathColumnIndex)).getValue().toString(); } catch (IllegalAccessException | InvocationTargetException ex) { - System.out.println("THROW"); + //WJS-TODO THROW SOMETHING } } 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; } @@ -162,8 +150,8 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider final int rows = Math.min(100, outline.getRowCount()); for (int column = 0; column < outline.getColumnModel().getColumnCount(); column++) { - int columnWidthLimit = 800; - int columnWidth = 0; + int columnWidthLimit = 2000; + int columnWidth = 200; // find the maximum width needed to fit the values for the first 100 rows, at most for (int row = 0; row < rows; row++) { @@ -179,18 +167,19 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider } } + @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()) { - setRowSelectionAllowed(false); + 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.MultiUserCasesPanel_caseListLoading_message()); + EmptyNode emptyNode = new EmptyNode(Bundle.CaseBrowser_caseListLoading_message()); em.setRootContext(emptyNode); - tableWorker = new LoadCaseListWorker(); + tableWorker = new LoadCaseMapWorker(); tableWorker.execute(); } @@ -205,22 +194,27 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider // //GEN-BEGIN:initComponents private void initComponents() { - jScrollPane1 = new javax.swing.JScrollPane(); + caseTableScrollPane = new javax.swing.JScrollPane(); setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); setLayout(new java.awt.BorderLayout()); - jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 5)); - jScrollPane1.setOpaque(false); - jScrollPane1.setPreferredSize(new java.awt.Dimension(5, 5)); - add(jScrollPane1, java.awt.BorderLayout.CENTER); + 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 jScrollPane1; + private javax.swing.JScrollPane caseTableScrollPane; // End of variables declaration//GEN-END:variables - private class LoadCaseListWorker extends SwingWorker { + + /** + * Swingworker to fetch the updated map of cases and their status in a + * background thread + */ + private class LoadCaseMapWorker extends SwingWorker { private static final String ALERT_FILE_NAME = "autoingest.alert"; private Map cases; @@ -314,11 +308,11 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider @Override protected void done() { - EventQueue.invokeLater(() -> { - CaseNode caseListNode = new CaseNode(cases); + MultiUserNode caseListNode = new MultiUserNode(cases); em.setRootContext(caseListNode); - setRowSelectionAllowed(true); + setColumnWidths(); + outline.setRowSelectionAllowed(true); }); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index 5eaa5bed6d..a2a2ec13ad 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -30,19 +30,18 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableRowSorter; import org.openide.util.Lookup; -import org.openide.util.NbBundle; 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. */ -@NbBundle.Messages({"MultiUserCasesPanel.caseListLoading.message=Please Wait..."}) final class MultiUserCasesPanel 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 caseListPanel; + private final CaseBrowser caseBrowserPanel; /** * Constructs a panel that allows a user to open cases created by automated @@ -52,16 +51,14 @@ final class MultiUserCasesPanel extends JPanel{ this.parentDialog = parentDialog; initComponents(); - caseListPanel = new CaseBrowser(); - caseListPanel.open(); - caseListPanel.setRowSelectionAllowed(false); - caseExplorerScrollPane.add(caseListPanel); - caseExplorerScrollPane.setViewportView(caseListPanel); + caseBrowserPanel = new CaseBrowser(); + caseExplorerScrollPane.add(caseBrowserPanel); + caseExplorerScrollPane.setViewportView(caseBrowserPanel); /* * Listen for row selection changes and set button state for the current * selection. */ - caseListPanel.addListSelectionListener((ListSelectionEvent e) -> { + caseBrowserPanel.addListSelectionListener((ListSelectionEvent e) -> { setButtons(); }); @@ -72,7 +69,7 @@ final class MultiUserCasesPanel extends JPanel{ * refreshes the cases table. */ void refresh() { - caseListPanel.refresh(); + caseBrowserPanel.refresh(); } /** @@ -80,7 +77,7 @@ final class MultiUserCasesPanel extends JPanel{ * in the cases table. */ void setButtons() { - bnOpen.setEnabled(caseListPanel.isRowSelected()); + bnOpen.setEnabled(caseBrowserPanel.isRowSelected()); } /** @@ -229,7 +226,7 @@ final class MultiUserCasesPanel extends JPanel{ * @param evt -- The event that caused this to be called */ private void bnOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenActionPerformed - openCase(caseListPanel.getCasePath()); + openCase(caseBrowserPanel.getCasePath()); }//GEN-LAST:event_bnOpenActionPerformed private void bnOpenSingleUserCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenSingleUserCaseActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index 87862d4d01..c410afb71b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,37 +40,31 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; /** - * Provides a root node for the results views with a single child node that - * displays a message as the sole item in its property sheet, useful for - * displaying explanatory text in the result views when there is a node with no - * children in the tree view. + * A root node containing child nodes of the multi user cases */ -public final class CaseNode extends AbstractNode { +public final class MultiUserNode extends AbstractNode { @Messages({"CaseNode.column.name=Name", "CaseNode.column.createdTime=Created Time", "CaseNode.column.status=Status", "CaseNode.column.metadataFilePath=Path"}) - private static final Logger LOGGER = Logger.getLogger(CaseNode.class.getName()); + private static final Logger LOGGER = Logger.getLogger(MultiUserNode.class.getName()); /** - * Provides a root node for the results views with a single child node that - * displays a message as the sole item in its property sheet, useful for - * displaying explanatory text in the result views when there is a node with - * no children in the tree view. + * Provides a root node with children which each represent a case. * - * @param displayedMessage The text for the property sheet of the child - * node. + * @param caseMap the map of cases and a boolean indicating if they have an + * alert */ - CaseNode(Map caseList) { - super(Children.create(new CaseNodeChildren(caseList), true)); + MultiUserNode(Map caseMap) { + super(Children.create(new MultiUserNodeChildren(caseMap), true)); } - static class CaseNodeChildren extends ChildFactory> { + static class MultiUserNodeChildren extends ChildFactory> { private final Map caseMap; - CaseNodeChildren(Map caseMap) { + MultiUserNodeChildren(Map caseMap) { this.caseMap = caseMap; } @@ -84,31 +78,30 @@ public final class CaseNode extends AbstractNode { @Override protected Node createNodeForKey(Entry key) { - return new CaseNameNode(key); + return new MultiUserCaseNode(key); } } /** - * The single child node of an EmptyNode, responsible for displaying a - * message as the sole item in its property sheet. + * A node which represents a single multi user case. */ - public static final class CaseNameNode extends DisplayableItemNode { + public static final class MultiUserCaseNode extends DisplayableItemNode { private final String caseName; private final String caseCreatedDate; private final String caseMetadataFilePath; private final boolean caseHasAlert; - CaseNameNode(Entry userCase) { + MultiUserCaseNode(Entry multiUserCase) { super(Children.LEAF); - caseName = userCase.getKey().getCaseDisplayName(); - caseCreatedDate = userCase.getKey().getCreatedDate(); - caseHasAlert = userCase.getValue(); + caseName = multiUserCase.getKey().getCaseDisplayName(); + caseCreatedDate = multiUserCase.getKey().getCreatedDate(); + caseHasAlert = multiUserCase.getValue(); super.setName(caseName); setName(caseName); setDisplayName(caseName); - caseMetadataFilePath = userCase.getKey().getFilePath().toString(); + caseMetadataFilePath = multiUserCase.getKey().getFilePath().toString(); } @Override @@ -152,14 +145,14 @@ public final class CaseNode extends AbstractNode { @Override public Action[] getActions(boolean context) { List actions = new ArrayList<>(); - // actions.addAll(Arrays.asList(super.getActions(context))); - actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); + actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); //open case context menu option return actions.toArray(new Action[actions.size()]); } } /** - * An action that opens the case node which it was generated off of + * An action that opens the specified case and hides the multi user case + * panel. */ private static final class OpenMultiUserCaseAction extends AbstractAction { @@ -188,10 +181,7 @@ public final class CaseNode extends AbstractNode { SwingUtilities.invokeLater(() -> { //GUI changes done back on the EDT StartupWindowProvider.getInstance().open(); - }); - } finally { - SwingUtilities.invokeLater(() -> { - //GUI changes done back on the EDT + MultiUserCasesDialog.getInstance().setVisible(true); }); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index d95c8f099e..f6d461e578 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; -import org.sleuthkit.autopsy.casemodule.CaseNode; +import org.sleuthkit.autopsy.casemodule.MultiUserNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; @@ -159,7 +159,7 @@ public interface DisplayableItemNodeVisitor { T visit(EmptyNode.MessageNode emptyNode); - T visit(CaseNode.CaseNameNode caseNode); + T visit(MultiUserNode.MultiUserCaseNode caseNode); T visit(InterestingHits.InterestingItemTypeNode aThis); @@ -245,7 +245,7 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(ftByMimeTypeEmptyNode); } @Override - public T visit(CaseNode.CaseNameNode caseNameNode) { + public T visit(MultiUserNode.MultiUserCaseNode caseNameNode) { return defaultVisit(caseNameNode); } @Override From bde7c89f8fa759db677912a7b40829f5d5bb7b90 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 12:44:38 -0500 Subject: [PATCH 09/14] 3408 remove unnecessary method, add coments --- .../autopsy/casemodule/CaseBrowser.java | 39 ++++--------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 4c7980fe32..6ecb0e849d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -80,6 +80,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); customize(); } @@ -88,27 +89,28 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider * 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 hidden column and default sorting column + 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 will need to ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex); ((ETableColumnModel) columnModel).setColumnHidden(column, true); outline.setRootVisible(false); - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); - outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - outline.setColumnSorted(dateColumnIndex, false, 1); //it would be nice if the column index wasn't hardcoded + //Sort on Created date column in descending order by default + outline.setColumnSorted(dateColumnIndex, false, 1); if (null == em) { em = new ExplorerManager(); } caseTableScrollPane.setViewportView(outlineView); - setColumnWidths(); this.setVisible(true); outline.setRowSelectionAllowed(false); } @@ -136,37 +138,13 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider /** * 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; } - private void setColumnWidths() { - int margin = 4; - int padding = 8; - - final int rows = Math.min(100, outline.getRowCount()); - - for (int column = 0; column < outline.getColumnModel().getColumnCount(); column++) { - int columnWidthLimit = 2000; - int columnWidth = 200; - - // find the maximum width needed to fit the values for the first 100 rows, at most - for (int row = 0; row < rows; row++) { - TableCellRenderer renderer = outline.getCellRenderer(row, column); - Component comp = outline.prepareRenderer(renderer, row, column); - columnWidth = Math.max(comp.getPreferredSize().width, columnWidth); - } - - columnWidth += 2 * margin + padding; // add margin and regular padding - columnWidth = Math.min(columnWidth, columnWidthLimit); - - outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth); - } - } - @NbBundle.Messages({"CaseBrowser.caseListLoading.message=Please Wait..."}) /** * Gets the list of cases known to the review mode cases manager and @@ -311,7 +289,6 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider EventQueue.invokeLater(() -> { MultiUserNode caseListNode = new MultiUserNode(cases); em.setRootContext(caseListNode); - setColumnWidths(); outline.setRowSelectionAllowed(true); }); } From d88aabf9a2daefc3a72b6dfd155e9b91c1ba87e5 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 13:56:09 -0500 Subject: [PATCH 10/14] 3408 basic context menu action for opening log file --- .../autopsy/casemodule/MultiUserNode.java | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index c410afb71b..8723212801 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -18,7 +18,11 @@ */ 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.Map; @@ -26,6 +30,7 @@ import java.util.Map.Entry; 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; @@ -49,6 +54,7 @@ public final class MultiUserNode extends AbstractNode { "CaseNode.column.status=Status", "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. @@ -92,6 +98,7 @@ public final class MultiUserNode extends AbstractNode { private final String caseCreatedDate; private final String caseMetadataFilePath; private final boolean caseHasAlert; + private final Path caseLogFilePath; MultiUserCaseNode(Entry multiUserCase) { super(Children.LEAF); @@ -102,6 +109,7 @@ public final class MultiUserNode extends AbstractNode { setName(caseName); setDisplayName(caseName); caseMetadataFilePath = multiUserCase.getKey().getFilePath().toString(); + caseLogFilePath = Paths.get(multiUserCase.getKey().getCaseDirectory(), LOG_FILE_NAME); } @Override @@ -123,6 +131,7 @@ public final class MultiUserNode extends AbstractNode { return caseMetadataFilePath; } + @Messages({"MultiUserNode.AlertColumn.text=Alert"}) //text to display when there is an alert present @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -136,7 +145,7 @@ public final class MultiUserNode extends AbstractNode { ss.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), caseCreatedDate)); ss.put(new NodeProperty<>(Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), - (caseHasAlert == true ? "Alert" : ""))); + (caseHasAlert == true ? Bundle.MultiUserNode_AlertColumn_text() : ""))); ss.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), caseMetadataFilePath)); return s; @@ -146,10 +155,14 @@ public final class MultiUserNode extends AbstractNode { public Action[] getActions(boolean context) { List actions = new ArrayList<>(); actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); //open case context menu option + if (caseLogFilePath != null && caseLogFilePath.toFile().exists()) { + 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. @@ -161,7 +174,7 @@ public final class MultiUserNode extends AbstractNode { private final String caseMetadataFilePath; OpenMultiUserCaseAction(String path) { - super("Open Case"); + super(Bundle.MultiUserNode_OpenMultiUserCaseAction_text()); caseMetadataFilePath = path; } @@ -193,5 +206,48 @@ public final class MultiUserNode extends AbstractNode { 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; + } + + @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(MultiUserCasesPanel.class, "DisplayLogDialog.cannotFindLog"), + org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.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(MultiUserCasesPanel.class, "DisplayLogDialog.cannotOpenLog"), + org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.unableToShowLogFile"), + JOptionPane.PLAIN_MESSAGE); + } + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } } From d171f89ffd1ac9c51d8144589f98b93b5ef353db Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 14:16:36 -0500 Subject: [PATCH 11/14] 3408 disable open log file option instead of hide it when not available --- .../autopsy/casemodule/MultiUserNode.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index 8723212801..d6fb271dd6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -155,9 +155,7 @@ public final class MultiUserNode extends AbstractNode { public Action[] getActions(boolean context) { List actions = new ArrayList<>(); actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); //open case context menu option - if (caseLogFilePath != null && caseLogFilePath.toFile().exists()) { - actions.add(new OpenCaseLogAction(caseLogFilePath)); - } + actions.add(new OpenCaseLogAction(caseLogFilePath)); return actions.toArray(new Action[actions.size()]); } } @@ -206,6 +204,7 @@ public final class MultiUserNode extends AbstractNode { 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 @@ -220,6 +219,7 @@ public final class MultiUserNode extends AbstractNode { OpenCaseLogAction(Path caseLogFilePath) { super(Bundle.MultiUserNode_OpenCaseLogAction_text()); pathToLog = caseLogFilePath; + this.setEnabled(caseLogFilePath != null && caseLogFilePath.toFile().exists()); } @Override @@ -231,14 +231,14 @@ public final class MultiUserNode extends AbstractNode { Desktop.getDesktop().edit(pathToLog.toFile()); } else { - JOptionPane.showMessageDialog(MultiUserCasesDialog.getInstance(), org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.cannotFindLog"), - org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE); + 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(MultiUserCasesPanel.class, "DisplayLogDialog.cannotOpenLog"), - org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "DisplayLogDialog.unableToShowLogFile"), + org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotOpenLog"), + org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.PLAIN_MESSAGE); } } From b82d5b5c5b23c95336e3070cb3fd8881910e1b53 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 15:49:37 -0500 Subject: [PATCH 12/14] 3408 remove unnecessary use of DisplayableItemNode --- .../autopsy/casemodule/MultiUserNode.java | 25 ++----------------- .../datamodel/DisplayableItemNodeVisitor.java | 8 +----- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index d6fb271dd6..39f7059c42 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -40,14 +40,12 @@ 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.DisplayableItemNode; -import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; /** * A root node containing child nodes of the multi user cases */ -public final class MultiUserNode extends AbstractNode { +final class MultiUserNode extends AbstractNode { @Messages({"CaseNode.column.name=Name", "CaseNode.column.createdTime=Created Time", @@ -92,7 +90,7 @@ public final class MultiUserNode extends AbstractNode { /** * A node which represents a single multi user case. */ - public static final class MultiUserCaseNode extends DisplayableItemNode { + static final class MultiUserCaseNode extends AbstractNode { private final String caseName; private final String caseCreatedDate; @@ -112,25 +110,6 @@ public final class MultiUserNode extends AbstractNode { caseLogFilePath = Paths.get(multiUserCase.getKey().getCaseDirectory(), LOG_FILE_NAME); } - @Override - public boolean isLeafTypeNode() { - return true; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - public String getItemType() { - return getClass().getName(); - } - - public String getMetadataFilePath() { - return caseMetadataFilePath; - } - @Messages({"MultiUserNode.AlertColumn.text=Alert"}) //text to display when there is an alert present @Override protected Sheet createSheet() { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index f6d461e578..2713992139 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.datamodel; -import org.sleuthkit.autopsy.casemodule.MultiUserNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; @@ -159,8 +158,6 @@ public interface DisplayableItemNodeVisitor { T visit(EmptyNode.MessageNode emptyNode); - T visit(MultiUserNode.MultiUserCaseNode caseNode); - T visit(InterestingHits.InterestingItemTypeNode aThis); /** @@ -244,10 +241,7 @@ public interface DisplayableItemNodeVisitor { public T visit(EmptyNode.MessageNode ftByMimeTypeEmptyNode) { return defaultVisit(ftByMimeTypeEmptyNode); } - @Override - public T visit(MultiUserNode.MultiUserCaseNode caseNameNode) { - return defaultVisit(caseNameNode); - } + @Override public T visit(InterestingHits.InterestingItemTypeNode interestingItemTypeNode) { return defaultVisit(interestingItemTypeNode); From 8f7491eb9baa775c9df702c94558b090eca6582a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 15:58:49 -0500 Subject: [PATCH 13/14] 3408 allow support for opening cases with double click again --- .../org/sleuthkit/autopsy/casemodule/MultiUserNode.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index 39f7059c42..9443ae35e9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -109,6 +109,15 @@ final class MultiUserNode extends AbstractNode { caseMetadataFilePath = multiUserCase.getKey().getFilePath().toString(); caseLogFilePath = Paths.get(multiUserCase.getKey().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); + } @Messages({"MultiUserNode.AlertColumn.text=Alert"}) //text to display when there is an alert present @Override From b3c98ab2c0b818c3e8690f28a04c772d508f6ceb Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 10 Jan 2018 17:29:10 -0500 Subject: [PATCH 14/14] 3408 log exception which was being ignored when path not in case table --- Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 6ecb0e849d..9440ca1356 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -130,7 +130,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider try { return ((Node.Property) outline.getModel().getValueAt(outline.convertRowIndexToModel(selectedRows[0]), originalPathColumnIndex)).getValue().toString(); } catch (IllegalAccessException | InvocationTargetException ex) { - //WJS-TODO THROW SOMETHING + LOGGER.log(Level.SEVERE, "Unable to get case path from table.", ex); } } return null;