From ef87e9a7b6c41918a8d4b954b95c78097133ba43 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 9 Jan 2018 16:15:08 -0500 Subject: [PATCH 01/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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; From 8fe3d587dd18195bbbaa96076f02ada8f8e0b31f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 11 Jan 2018 15:40:09 -0500 Subject: [PATCH 15/36] 3408 make search tip more helpful and fix comments --- .../org/sleuthkit/autopsy/casemodule/Bundle.properties | 2 +- .../org/sleuthkit/autopsy/casemodule/CaseBrowser.java | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index ab2b5c12dc..2d87cd9a06 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -229,5 +229,5 @@ 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.searchLabel.text=Select any case and start typing to search by case name MultiUserCasesPanel.cancelButton.text=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 9440ca1356..b25a231e83 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -18,11 +18,9 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.Component; import java.lang.reflect.InvocationTargetException; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; -import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; @@ -100,7 +98,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider dateColumnIndex = index; } } - //Hide path column by default will need to + //Hide path column by default (user can unhide it) ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex); ((ETableColumnModel) columnModel).setColumnHidden(column, true); outline.setRootVisible(false); @@ -124,6 +122,11 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider outline.getSelectionModel().addListSelectionListener(listener); } + /** + * Get the path to the .aut file for the selected case. + * + * @return the full path to the selected case's .aut file + */ String getCasePath() { int[] selectedRows = outline.getSelectedRows(); if (selectedRows.length == 1) { From 48e338d288b3f746b361702c03e7b999a818a8df Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Fri, 19 Jan 2018 16:34:32 +0000 Subject: [PATCH 16/36] changed names of the jar files in autopsy core build --- Core/build.xml | 6 ++++-- Core/nbproject/project.properties | 2 +- Core/nbproject/project.xml | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Core/build.xml b/Core/build.xml index c22d725cce..e75c217d15 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -17,6 +17,8 @@ + + @@ -52,8 +54,8 @@ - + + net.sf.sevenzipjbinding net.sf.sevenzipjbinding.impl @@ -333,8 +334,8 @@ release/modules/ext/tika-core-1.14.jar - ext/Tsk_DataModel_PostgreSQL.jar - release/modules/ext/Tsk_DataModel_PostgreSQL.jar + ext/sleuthkit-postgresql-4.6.0.jar + release/modules/ext/sleuthkit-postgresql-4.6.0.jar ext/opencv-248.jar From bc8642dbdb509221aac6fb355a9b6e58303d8582 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 16 Jan 2018 07:14:06 -0500 Subject: [PATCH 17/36] Merge pull request #3362 from APriestman/3165_liveTriageButton 3165 Added menu item to create live triage drive --- .../autopsy/livetriage/Bundle.properties | 7 + .../CreateLiveTriageDriveAction.java | 284 +++++++++++++ .../autopsy/livetriage/SelectDriveDialog.form | 163 ++++++++ .../autopsy/livetriage/SelectDriveDialog.java | 388 ++++++++++++++++++ 4 files changed, 842 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties new file mode 100644 index 0000000000..61f522a6b4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties @@ -0,0 +1,7 @@ +SelectDriveDialog.bnOk.text=Ok +SelectDriveDialog.bnRefresh.text=Refresh +SelectDriveDialog.lbSelectDrive.text=Select the drive to copy the application and script to: +SelectDriveDialog.jLabel1.text=Select drive to use for live triage (may take time to load): +SelectDriveDialog.errorLabel.text=jLabel2 +SelectDriveDialog.bnCancel.text=Cancel +SelectDriveDialog.jTextArea1.text=This feature copies the application and a batch file to a removable drive,\nallowing systems to be analyzed without installing the software or\nimaging the drives.\n\nTo analyze a system, insert the drive and run "RunFromUSB.bat" as\nadministrator, then select the "Local Disk" option on the Add Data Source\npanel. diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java new file mode 100644 index 0000000000..846632a092 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -0,0 +1,284 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 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.livetriage; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.InvalidPathException; +import java.util.logging.Level; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeEvent; +import javax.swing.JOptionPane; +import java.awt.Frame; +import javax.swing.SwingWorker; +import org.apache.commons.io.FileUtils; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; + +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.livetriage.CreateLiveTriageDriveAction") +@ActionReference(path = "Menu/Tools", position = 1401) +@ActionRegistration(displayName = "#CTL_CreateLiveTriageDriveAction", lazy = false) +@NbBundle.Messages({"CTL_CreateLiveTriageDriveAction=Make Live Triage Drive"}) +public final class CreateLiveTriageDriveAction extends CallableSystemAction implements PropertyChangeListener { + + private static final String DISPLAY_NAME = Bundle.CTL_CreateLiveTriageDriveAction(); + private ModalDialogProgressIndicator progressIndicator = null; + private String drivePath = ""; + private CopyFilesWorker worker; + + @Override + public boolean isEnabled() { + return true; + } + + @NbBundle.Messages({"CreateLiveTriageDriveAction.error.title=Error creating live triage disk", + "CreateLiveTriageDriveAction.exenotfound.message=Executable could not be found", + "CreateLiveTriageDriveAction.batchFileError.message=Error creating batch file", + "CreateLiveTriageDriveAction.appPathError.message=Could not location application directory", + "CreateLiveTriageDriveAction.copyError.message=Could not copy application. Only works on installed version.", + "CreateLiveTriageDriveAction.success.title=Success", + "CreateLiveTriageDriveAction.success.message=Live triage drive created. Use RunFromUSB.bat to run the application" + }) + @Override + @SuppressWarnings("fallthrough") + public void performAction() { + + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + + // If this is an installed version, there should be an 64.exe file in the bin folder + String appName = UserPreferences.getAppName(); + String exeName = appName + "64.exe"; + String installPath = PlatformUtil.getInstallPath(); + + Path exePath = Paths.get(installPath, "bin", exeName); + + if (!exePath.toFile().exists()) { + JOptionPane.showMessageDialog(mainWindow, + Bundle.CreateLiveTriageDriveAction_exenotfound_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + Path applicationBasePath; + try { + applicationBasePath = exePath.getParent().getParent(); + } catch (InvalidPathException ex) { + JOptionPane.showMessageDialog(mainWindow, + Bundle.CreateLiveTriageDriveAction_appPathError_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + SelectDriveDialog driveDialog = new SelectDriveDialog(mainWindow, true); + driveDialog.display(); + + if (!driveDialog.getSelectedDrive().isEmpty()) { + drivePath = driveDialog.getSelectedDrive(); + if (drivePath.startsWith("\\\\.\\")) { + drivePath = drivePath.substring(4); + } + + worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); + worker.addPropertyChangeListener(this); + worker.execute(); + } + } + + @NbBundle.Messages({"# {0} - drivePath", + "CreateLiveTriageDriveAction.progressBar.text=Copying live triage files to {0}", + "CreateLiveTriageDriveAction.progressBar.title=Please wait"}) + @Override + public void propertyChange(PropertyChangeEvent evt) { + + if ("state".equals(evt.getPropertyName()) + && (SwingWorker.StateValue.STARTED.equals(evt.getNewValue()))) { + + // Setup progress bar. + String displayStr = NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.text", + drivePath); + + progressIndicator = new ModalDialogProgressIndicator(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.title")); + progressIndicator.start(displayStr); + + } else if ("state".equals(evt.getPropertyName()) + && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) { + if (progressIndicator != null) { + progressIndicator.finish(); + } + + if (worker.hadError()) { + MessageNotifyUtil.Message.error(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); + } else { + MessageNotifyUtil.Message.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); + } + } + } + + private class CopyFilesWorker extends SwingWorker { + + private final Path sourceFolder; + private final String drivePath; + private final String appName; + private boolean error = false; + + CopyFilesWorker(Path sourceFolder, String drivePath, String appName) { + this.sourceFolder = sourceFolder; + this.drivePath = drivePath; + this.appName = appName; + } + + boolean hadError() { + return error; + } + + @Override + protected Void doInBackground() throws Exception { + + copyBatchFile(drivePath, appName); + copyApplication(sourceFolder, drivePath, appName); + + return null; + } + + @NbBundle.Messages({"CopyFilesWorker.error.text=Error copying live triage files", + "CopyFilesWorker.done.text=Finished creating live triage disk"}) + @Override + protected void done() { + try { + super.get(); + } catch (Exception ex) { + error = true; + Logger.getLogger(CreateLiveTriageDriveAction.class.getName()).log(Level.SEVERE, "Fatal error during live triage drive creation", ex); //NON-NLS + } + } + } + + private void copyApplication(Path sourceFolder, String destBaseFolder, String appName) throws IOException { + + // Create an appName folder in the destination + Path destAppFolder = Paths.get(destBaseFolder, appName); + if (!destAppFolder.toFile().exists()) { + if (!destAppFolder.toFile().mkdirs()) { + throw new IOException("Failed to create directory " + destAppFolder.toString()); + } + } + + // Now copy the files + FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile()); + } + + private void copyBatchFile(String destPath, String appName) throws IOException, InvalidPathException { + Path batchFilePath = Paths.get(destPath, "RunFromUSB.bat"); + FileUtils.writeStringToFile(batchFilePath.toFile(), getBatchFileContents(appName), "UTF-8"); + + } + + private String getBatchFileContents(String appName) { + + String batchFile + = "@echo off\n" + + "\n" + + "REM This restores the working directory when using 'Run as administrator'" + + "@setlocal enableextensions\n" + + "@cd /d \"%~dp0\"" + + "\n" + + "SET appName=\"" + appName + "\"\n" + + "\n" + + "REM Create the configData directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData mkdir configData\n" + + "if not exist configData (\n" + + " echo Error creating directory configData\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the userdir sub directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData\\userdir mkdir configData\\userdir\n" + + "if not exist configData\\userdir (\n" + + " echo Error creating directory configData\\userdir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cachedir sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\cachedir rd /s /q configData\\cachedir\n" + + "mkdir configData\\cachedir\n" + + "if not exist configData\\cachedir (\n" + + " echo Error creating directory configData\\cachedir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the temp sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\temp rd /s /q configData\\temp\n" + + "mkdir configData\\temp\n" + + "if not exist configData\\temp (\n" + + " echo Error creating directory configData\\temp\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cases directory. It's ok if this fails.\n" + + "if not exist cases mkdir cases\n" + + "\n" + + "if exist %appName% (\n" + + " if not exist %appName%\\bin\\%appName%64.exe (\n" + + " echo %appName%\\bin\\%appName%64.exe does not exist\n" + + " goto end\n" + + " )\n" + + " %appName%\\bin\\%appName%64.exe --userdir ..\\configData\\userdir --cachedir ..\\configData\\cachedir -J-Djava.io.tmpdir=..\\configData\\temp\n" + + ") else (\n" + + " echo Could not find %appName% directory\n" + + " goto end\n" + + ")\n" + + "\n" + + ":end\n" + + "\n" + + "REM Keep the cmd window open in case there was an error\n" + + "@pause\n"; + return batchFile; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } +} diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form new file mode 100644 index 0000000000..ee776348dc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form @@ -0,0 +1,163 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java new file mode 100644 index 0000000000..c13d82d324 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java @@ -0,0 +1,388 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.livetriage; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.awt.Dimension; +import java.awt.Point; +import javax.swing.SwingWorker; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.LocalDisk; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * + */ +class SelectDriveDialog extends javax.swing.JDialog { + + private List disks = new ArrayList<>(); + private final LocalDiskModel model = new LocalDiskModel(); + private final java.awt.Frame parent; + private String drivePath = ""; + + /** + * Creates new form SelectDriveDialog + */ + @NbBundle.Messages({"SelectDriveDialog.title=Create Live Triage Drive"}) + SelectDriveDialog(java.awt.Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + this.parent = parent; + + model.loadDisks(); + bnOk.setEnabled(false); + diskTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (diskTable.getSelectedRow() >= 0 && diskTable.getSelectedRow() < disks.size()) { + bnOk.setEnabled(true); + } else { //The selection changed to nothing valid being selected, such as with ctrl+click + bnOk.setEnabled(false); + } + } + }); + } + + void display() { + this.setTitle(Bundle.SelectDriveDialog_title()); + + final Dimension parentSize = parent.getSize(); + final Point parentLocationOnScreen = parent.getLocationOnScreen(); + final Dimension childSize = this.getSize(); + int x; + int y; + x = (parentSize.width - childSize.width) / 2; + y = (parentSize.height - childSize.height) / 2; + x += parentLocationOnScreen.x; + y += parentLocationOnScreen.y; + + setLocation(x, y); + setVisible(true); + } + + String getSelectedDrive() { + return this.drivePath; + } + + /** + * 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(); + diskTable = new javax.swing.JTable(); + jLabel1 = new javax.swing.JLabel(); + bnRefresh = new javax.swing.JButton(); + bnOk = new javax.swing.JButton(); + errorLabel = new javax.swing.JLabel(); + jSeparator1 = new javax.swing.JSeparator(); + bnCancel = new javax.swing.JButton(); + jScrollPane2 = new javax.swing.JScrollPane(); + jTextArea1 = new javax.swing.JTextArea(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + diskTable.setModel(model); + jScrollPane1.setViewportView(diskTable); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnRefresh.text")); // NOI18N + bnRefresh.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnRefreshActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(bnOk, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnOk.text")); // NOI18N + bnOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnOkActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.errorLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnCancel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnCancel.text")); // NOI18N + bnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnCancelActionPerformed(evt); + } + }); + + jScrollPane2.setBorder(null); + + jTextArea1.setBackground(new java.awt.Color(240, 240, 240)); + jTextArea1.setColumns(20); + jTextArea1.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + jTextArea1.setRows(5); + jTextArea1.setText(org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jTextArea1.text")); // NOI18N + jTextArea1.setBorder(null); + jScrollPane2.setViewportView(jTextArea1); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(bnRefresh, javax.swing.GroupLayout.DEFAULT_SIZE, 112, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) + .addComponent(bnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane2)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 112, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bnRefresh) + .addComponent(bnCancel) + .addComponent(bnOk)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void bnRefreshActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnRefreshActionPerformed + model.loadDisks(); + }//GEN-LAST:event_bnRefreshActionPerformed + + private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed + if (diskTable.getSelectedRow() >= 0 && diskTable.getSelectedRow() < disks.size()) { + LocalDisk selectedDisk = disks.get(diskTable.getSelectedRow()); + drivePath = selectedDisk.getPath(); + } else { + drivePath = ""; + } + dispose(); + }//GEN-LAST:event_bnOkActionPerformed + + private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed + dispose(); + }//GEN-LAST:event_bnCancelActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton bnCancel; + private javax.swing.JButton bnOk; + private javax.swing.JButton bnRefresh; + private javax.swing.JTable diskTable; + private javax.swing.JLabel errorLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JTextArea jTextArea1; + // End of variables declaration//GEN-END:variables + + /** + * Table model for displaying information from LocalDisk Objects in a table. + */ + @NbBundle.Messages({"SelectDriveDialog.localDiskModel.loading.msg=", + "SelectDriveDialog.localDiskModel.nodrives.msg=Executable could not be found", + "SelectDriveDialog.diskTable.column1.title=Disk Name", + "SelectDriveDialog.diskTable.column2.title=Disk Size", + "SelectDriveDialog.errLabel.disksNotDetected.text=Disks were not detected. On some systems it requires admin privileges", + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText=Disks were not detected." + + }) + private class LocalDiskModel implements TableModel { + + private LocalDiskThread worker = null; + private boolean ready = false; + private volatile boolean loadingDisks = false; + + //private String SELECT = "Select a local disk:"; + private final String LOADING = NbBundle.getMessage(this.getClass(), "SelectDriveDialog.localDiskModel.loading.msg"); + private final String NO_DRIVES = NbBundle.getMessage(this.getClass(), "SelectDriveDialog.localDiskModel.nodrives.msg"); + + private void loadDisks() { + + // if there is a worker already building the lists, then cancel it first. + if (loadingDisks && worker != null) { + worker.cancel(false); + } + + // Clear the lists + errorLabel.setText(""); + diskTable.setEnabled(false); + ready = false; + loadingDisks = true; + worker = new LocalDiskThread(); + worker.execute(); + } + + @Override + public int getRowCount() { + if (disks.isEmpty()) { + return 0; + } + return disks.size(); + } + + @Override + public int getColumnCount() { + return 2; + + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return NbBundle.getMessage(this.getClass(), "SelectDriveDialog.diskTable.column1.title"); + case 1: + return NbBundle.getMessage(this.getClass(), "SelectDriveDialog.diskTable.column2.title"); + default: + return "Unnamed"; //NON-NLS + } + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (ready) { + if (disks.isEmpty()) { + return NO_DRIVES; + } + switch (columnIndex) { + case 0: + return disks.get(rowIndex).getName(); + case 1: + return disks.get(rowIndex).getReadableSize(); + default: + return disks.get(rowIndex).getPath(); + } + } else { + return LOADING; + } + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + //setter does nothing they should not be able to modify table + } + + @Override + public void addTableModelListener(TableModelListener l) { + + } + + @Override + public void removeTableModelListener(TableModelListener l) { + + } + + /** + * Gets the lists of physical drives and partitions and combines them + * into a list of disks. + */ + class LocalDiskThread extends SwingWorker { + + private final Logger logger = Logger.getLogger(LocalDiskThread.class.getName()); + private List partitions = new ArrayList<>(); + + @Override + protected Object doInBackground() throws Exception { + // Populate the lists + partitions = new ArrayList<>(); + partitions = PlatformUtil.getPartitions(); + return null; + } + + /** + * Display any error messages that might of occurred when getting + * the lists of physical drives or partitions. + */ + private void displayErrors() { + if (partitions.isEmpty()) { + if (PlatformUtil.isWindowsOS()) { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.disksNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText")); + } else { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.drivesNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.drivesNotDetected.toolTipText")); + } + errorLabel.setVisible(true); + diskTable.setEnabled(false); + } + } + + @Override + protected void done() { + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Loading local disks was canceled."); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Loading local disks was interrupted."); //NON-NLS + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error when loading local disks", ex); //NON-NLS + } finally { + if (!this.isCancelled()) { + displayErrors(); + worker = null; + loadingDisks = false; + disks = new ArrayList<>(); + disks.addAll(partitions); + if (disks.size() > 0) { + diskTable.setEnabled(true); + diskTable.clearSelection(); + } + ready = true; + } + } + diskTable.revalidate(); + } + } + } +} From 06132c8e825f303c33120cdaa99a59a938addbd7 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Mon, 22 Jan 2018 12:22:21 -0500 Subject: [PATCH 18/36] 3448 vs Relase 4.6.0: retain more log files --- .../autopsy/core/UserPreferences.java | 28 +- .../corecomponents/AutopsyOptionsPanel.form | 145 ++++++--- .../corecomponents/AutopsyOptionsPanel.java | 278 +++++++++++------- .../autopsy/corecomponents/Bundle.properties | 1 + .../sleuthkit/autopsy/coreutils/Logger.java | 66 +---- 5 files changed, 325 insertions(+), 193 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 725c050563..81d207abae 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2017 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,7 +67,9 @@ public final class UserPreferences { private static final String APP_NAME = "AppName"; public static final String SETTINGS_PROPERTIES = "AutoIngest"; private static final String MODE = "AutopsyMode"; // NON-NLS - + private static final String MAX_NUM_OF_LOG_FILE = "MaximumNumberOfLogFiles"; + private static final int LOG_FILE_NUM_INT = 10; + // Prevent instantiation. private UserPreferences() { } @@ -369,4 +371,26 @@ public final class UserPreferences { preferences.put(APP_NAME, name); } + /** + * get the maximum number of log files to save + * @return Number of log files + */ + public static int getLogFileCount() { + return preferences.getInt(MAX_NUM_OF_LOG_FILE, LOG_FILE_NUM_INT); + } + + /** + * get the default number of log files to save + * @return LOG_FILE_COUNT + */ + public static int getDefaultLogFileCount() { + return LOG_FILE_NUM_INT; + } + /** + * Set the maximum number of log files to save + * @param count number of log files + */ + public static void setLogFileCount(int count) { + preferences.putInt(MAX_NUM_OF_LOG_FILE, count); + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form index c1fc384c5e..36b88c10ca 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form @@ -24,7 +24,7 @@ - + @@ -44,18 +44,26 @@ + + + + + - + - + - - + + + + + - + @@ -63,10 +71,11 @@ - - - - + + + + + @@ -245,7 +254,7 @@ - + @@ -436,28 +445,43 @@ - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + @@ -466,22 +490,29 @@ + - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -545,6 +576,40 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 17aad44582..f4e2a38671 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -73,7 +73,9 @@ import org.sleuthkit.autopsy.report.ReportBranding; "AutopsyOptionsPanel.browseLogosButton.text=Browse", "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.invalidPath.text=Path is not valid.", "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.invalidImageSpecified.text=Invalid image file specified.", - "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.pathNotSet.text=Agency logo path must be set." + "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.pathNotSet.text=Agency logo path must be set.", + "AutopsyOptionsPanel.maxLogFileCount.text=Maximum Log Files:", + "AutopsyOptionsPanel.logNumAlert.invalidInput.text=A positive integer is required here." }) final class AutopsyOptionsPanel extends javax.swing.JPanel { @@ -110,6 +112,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { textFieldListener = new TextFieldListener(); agencyLogoPathField.getDocument().addDocumentListener(textFieldListener); + logFileCount.setText(String.valueOf(UserPreferences.getLogFileCount())); } /** @@ -309,6 +312,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { specifyLogoRB.setSelected(!useDefault); agencyLogoPathField.setEnabled(!useDefault); browseLogosButton.setEnabled(!useDefault); + logFileCount.setText(String.valueOf(UserPreferences.getLogFileCount())); try { updateAgencyLogo(path); } catch (IOException ex) { @@ -363,6 +367,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { UserPreferences.setHideSlackFilesInDataSourcesTree(dataSourcesHideSlackCB.isSelected()); UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCB.isSelected()); UserPreferences.setDisplayTimesInLocalTime(useLocalTimeRB.isSelected()); + UserPreferences.setLogFileCount(Integer.parseInt(logFileCount.getText())); if (!agencyLogoPathField.getText().isEmpty()) { File file = new File(agencyLogoPathField.getText()); if (file.exists()) { @@ -394,6 +399,9 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { if (!isMemFieldValid()) { valid = false; } + if (!isLogNumFieldValid()) { + valid = false; + } return valid; } @@ -476,6 +484,26 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { return true; } + /** + * Check if the logFileCount field is valid. + * + * @return true if the logFileCount is valid false if it is not + */ + private boolean isLogNumFieldValid() { + String count = logFileCount.getText(); + logNumAlert.setText(""); + try { + int count_num = Integer.parseInt(count); + if (count_num < 1) { + logNumAlert.setText(Bundle.AutopsyOptionsPanel_logNumAlert_invalidInput_text()); + return false; + } + } catch (NumberFormatException e) { + logNumAlert.setText(Bundle.AutopsyOptionsPanel_logNumAlert_invalidInput_text()); + return false; + } + return true; + } /** * Listens for registered text fields that have changed and fires a * PropertyChangeEvent accordingly. @@ -540,9 +568,14 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { memField = new javax.swing.JTextField(); memFieldValidationLabel = new javax.swing.JLabel(); maxMemoryUnitsLabel1 = new javax.swing.JLabel(); + maxLogFileCount = new javax.swing.JLabel(); + logFileCount = new javax.swing.JTextField(); + logNumAlert = new javax.swing.JTextField(); jScrollPane1.setBorder(null); + jPanel1.setPreferredSize(new java.awt.Dimension(671, 488)); + logoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.logoPanel.border.title"))); // NOI18N agencyLogoPathField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPathField.text")); // NOI18N @@ -712,7 +745,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { .addComponent(dataSourcesHideSlackCB) .addComponent(viewsHideSlackCB) .addComponent(useLocalTimeRB)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap(158, Short.MAX_VALUE)))) .addGroup(viewPanelLayout.createSequentialGroup() .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabelHideSlackFiles) @@ -774,68 +807,103 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(maxMemoryUnitsLabel1, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxMemoryUnitsLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(maxLogFileCount, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxLogFileCount.text")); // NOI18N + + logFileCount.setHorizontalAlignment(javax.swing.JTextField.TRAILING); + logFileCount.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + logFileCountKeyReleased(evt); + } + }); + + logNumAlert.setEditable(false); + logNumAlert.setFont(logNumAlert.getFont().deriveFont(logNumAlert.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + logNumAlert.setForeground(new java.awt.Color(255, 0, 0)); + logNumAlert.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.logNumAlert.text")); // NOI18N + logNumAlert.setBorder(null); + javax.swing.GroupLayout runtimePanelLayout = new javax.swing.GroupLayout(runtimePanel); runtimePanel.setLayout(runtimePanelLayout); runtimePanelLayout.setHorizontalGroup( runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(runtimePanelLayout.createSequentialGroup() .addContainerGap() - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(totalMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(memField, javax.swing.GroupLayout.DEFAULT_SIZE, 37, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE) - .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(totalMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(systemMemoryTotal, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(2, 2, 2) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.DEFAULT_SIZE, 333, Short.MAX_VALUE)) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 263, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)))) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxLogFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(logFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(logNumAlert))) + .addContainerGap()) ); runtimePanelLayout.setVerticalGroup( runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(runtimePanelLayout.createSequentialGroup() .addContainerGap() .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(maxMemoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(totalMemoryLabel) .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(totalMemoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(maxLogFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, 19, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(logFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(logNumAlert, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(logoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(runtimePanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(runtimePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addContainerGap()) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addGap(0, 0, 0) - .addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(runtimePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(runtimePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(logoPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); @@ -846,7 +914,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 1010, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -854,60 +922,14 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { ); }// //GEN-END:initComponents - private void useBestViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_useBestViewerRBActionPerformed - - private void keepCurrentViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_keepCurrentViewerRBActionPerformed - - private void dataSourcesHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_dataSourcesHideKnownCBActionPerformed - - private void viewsHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_viewsHideKnownCBActionPerformed - - private void useLocalTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_useLocalTimeRBActionPerformed - - private void useGMTTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useGMTTimeRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_useGMTTimeRBActionPerformed - - private void dataSourcesHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_dataSourcesHideSlackCBActionPerformed - - private void viewsHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideSlackCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_viewsHideSlackCBActionPerformed - - private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed - String oldLogoPath = agencyLogoPathField.getText(); - int returnState = fileChooser.showOpenDialog(this); - if (returnState == JFileChooser.APPROVE_OPTION) { - String path = fileChooser.getSelectedFile().getPath(); - try { - updateAgencyLogo(path); - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - } catch (IOException | IndexOutOfBoundsException ex) { - JOptionPane.showMessageDialog(null, - NbBundle.getMessage(this.getClass(), - "AutopsyOptionsPanel.invalidImageFile.msg"), - NbBundle.getMessage(this.getClass(), "AutopsyOptionsPanel.invalidImageFile.title"), - JOptionPane.ERROR_MESSAGE); - try { - updateAgencyLogo(oldLogoPath); //restore previous setting if new one is invalid - } catch (IOException ex1) { - LOGGER.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex1); - } - } + private void logFileCountKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_logFileCountKeyReleased + String count = logFileCount.getText(); + if (count.equals(String.valueOf(UserPreferences.getDefaultLogFileCount()))) { + //if it is still the default value don't fire change + return; } - }//GEN-LAST:event_browseLogosButtonActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_logFileCountKeyReleased private void memFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_memFieldKeyReleased String memText = memField.getText(); @@ -918,17 +940,37 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_memFieldKeyReleased - private void defaultLogoRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_defaultLogoRBActionPerformed - agencyLogoPathField.setEnabled(false); - browseLogosButton.setEnabled(false); - try { - updateAgencyLogo(""); - } catch (IOException ex) { - // This should never happen since we're not reading from a file. - LOGGER.log(Level.SEVERE, "Unexpected error occurred while updating the agency logo.", ex); - } + private void useGMTTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useGMTTimeRBActionPerformed firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_defaultLogoRBActionPerformed + }//GEN-LAST:event_useGMTTimeRBActionPerformed + + private void useLocalTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useLocalTimeRBActionPerformed + + private void viewsHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideSlackCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_viewsHideSlackCBActionPerformed + + private void dataSourcesHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_dataSourcesHideSlackCBActionPerformed + + private void viewsHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_viewsHideKnownCBActionPerformed + + private void dataSourcesHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_dataSourcesHideKnownCBActionPerformed + + private void keepCurrentViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_keepCurrentViewerRBActionPerformed + + private void useBestViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useBestViewerRBActionPerformed private void specifyLogoRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_specifyLogoRBActionPerformed agencyLogoPathField.setEnabled(true); @@ -946,6 +988,41 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_specifyLogoRBActionPerformed + private void defaultLogoRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_defaultLogoRBActionPerformed + agencyLogoPathField.setEnabled(false); + browseLogosButton.setEnabled(false); + try { + updateAgencyLogo(""); + } catch (IOException ex) { + // This should never happen since we're not reading from a file. + LOGGER.log(Level.SEVERE, "Unexpected error occurred while updating the agency logo.", ex); + } + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_defaultLogoRBActionPerformed + + private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed + String oldLogoPath = agencyLogoPathField.getText(); + int returnState = fileChooser.showOpenDialog(this); + if (returnState == JFileChooser.APPROVE_OPTION) { + String path = fileChooser.getSelectedFile().getPath(); + try { + updateAgencyLogo(path); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } catch (IOException | IndexOutOfBoundsException ex) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(this.getClass(), + "AutopsyOptionsPanel.invalidImageFile.msg"), + NbBundle.getMessage(this.getClass(), "AutopsyOptionsPanel.invalidImageFile.title"), + JOptionPane.ERROR_MESSAGE); + try { + updateAgencyLogo(oldLogoPath); //restore previous setting if new one is invalid + } catch (IOException ex1) { + LOGGER.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex1); + } + } + } + }//GEN-LAST:event_browseLogosButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField agencyLogoPathField; private javax.swing.JLabel agencyLogoPathFieldValidationLabel; @@ -963,8 +1040,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JRadioButton keepCurrentViewerRB; + private javax.swing.JTextField logFileCount; + private javax.swing.JTextField logNumAlert; private javax.swing.JPanel logoPanel; private javax.swing.ButtonGroup logoSourceButtonGroup; + private javax.swing.JLabel maxLogFileCount; private javax.swing.JLabel maxMemoryLabel; private javax.swing.JLabel maxMemoryUnitsLabel; private javax.swing.JLabel maxMemoryUnitsLabel1; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 6e9b722d71..1aecff7348 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -200,3 +200,4 @@ CriterionChooser.ascendingRadio.text=\u25b2 Ascending\n CriterionChooser.removeButton.text=Remove CriterionChooser.descendingRadio.text=\u25bc Descending AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.text= +AutopsyOptionsPanel.logNumAlert.text= diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java index 16fde58e29..710e7ff9a1 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2015 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +24,10 @@ import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.Handler; import java.sql.Timestamp; -import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.logging.LogRecord; +import org.sleuthkit.autopsy.core.UserPreferences; /** * Autopsy specialization of the Java Logger class with custom file handlers. @@ -37,55 +37,23 @@ public final class Logger extends java.util.logging.Logger { private static final String LOG_ENCODING = PlatformUtil.getLogFileEncoding(); private static final int LOG_SIZE = 0; // In bytes, zero is unlimited - private static final int LOG_FILE_COUNT = 10; - private static final String LOG_WITHOUT_STACK_TRACES = "autopsy.log"; //NON-NLS - private static final String LOG_WITH_STACK_TRACES = "autopsy_traces.log"; //NON-NLS + private static final String LOG_FILE_NAME = "autopsy.log"; //NON-NLS private static final Map namesToLoggers = new HashMap<>(); private static final Handler consoleHandler = new java.util.logging.ConsoleHandler(); - private static FileHandler userFriendlyHandler = createFileHandlerWithoutTraces(PlatformUtil.getLogDirectory()); - private static FileHandler developerFriendlyHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); - - /** - * Creates a custom file handler with a custom message formatter that does - * not include stack traces. - * - * @param logDirectory The directory where the log files should reside. - * - * @return A custom file handler. - */ - private static FileHandler createFileHandlerWithoutTraces(String logDirectory) { - String logFilePath = Paths.get(logDirectory, LOG_WITHOUT_STACK_TRACES).toString(); - try { - FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, LOG_FILE_COUNT); - fileHandler.setEncoding(LOG_ENCODING); - fileHandler.setFormatter(new Formatter() { - @Override - public String format(LogRecord record) { - return (new Date(record.getMillis())).toString() + " " - + record.getSourceClassName() + " " - + record.getSourceMethodName() + "\n" - + record.getLevel() + ": " - + this.formatMessage(record) + "\n"; - } - }); - return fileHandler; - } catch (IOException ex) { - throw new RuntimeException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS - } - } + private static FileHandler fileHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); /** * Creates a custom file handler with a custom message formatter that - * incldues stack traces. + * includes stack traces. * * @param logDirectory The directory where the log files should reside. * * @return A custom file handler. */ private static FileHandler createFileHandlerWithTraces(String logDirectory) { - String logFilePath = Paths.get(logDirectory, LOG_WITH_STACK_TRACES).toString(); + String logFilePath = Paths.get(logDirectory, LOG_FILE_NAME).toString(); try { - FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, LOG_FILE_COUNT); + FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, UserPreferences.getLogFileCount()); fileHandler.setEncoding(LOG_ENCODING); fileHandler.setFormatter(new Formatter() { @Override @@ -128,25 +96,20 @@ public final class Logger extends java.util.logging.Logger { * add/remove handler calls (currently, the base class handlers * collection is a CopyOnWriteArrayList). */ - FileHandler newUserFriendlyHandler = createFileHandlerWithoutTraces(directoryPath); - FileHandler newDeveloperFriendlyHandler = createFileHandlerWithTraces(directoryPath); + FileHandler newFileHandler = createFileHandlerWithTraces(directoryPath); for (Logger logger : namesToLoggers.values()) { - logger.addHandler(newUserFriendlyHandler); - logger.addHandler(newDeveloperFriendlyHandler); - logger.removeHandler(userFriendlyHandler); - logger.removeHandler(userFriendlyHandler); + logger.addHandler(newFileHandler); + logger.removeHandler(fileHandler); } /* * Close the old file handlers and save references to the new handlers * so they can be added to any new loggers. This swap is why this method * and the two overloads of getLogger() are synchronized, serializing - * access to userFriendlyHandler and developerFriendlyHandler. + * access to userFileHandler. */ - userFriendlyHandler.close(); - userFriendlyHandler = newUserFriendlyHandler; - developerFriendlyHandler.close(); - developerFriendlyHandler = newDeveloperFriendlyHandler; + fileHandler.close(); + fileHandler = newFileHandler; } /** @@ -178,8 +141,7 @@ public final class Logger extends java.util.logging.Logger { synchronized public static Logger getLogger(String name, String resourceBundleName) { if (!namesToLoggers.containsKey(name)) { Logger logger = new Logger(name, resourceBundleName); - logger.addHandler(userFriendlyHandler); - logger.addHandler(developerFriendlyHandler); + logger.addHandler(fileHandler); namesToLoggers.put(name, logger); } return namesToLoggers.get(name); From f472a7b2ae1787d3e1e884cdc8ceceb8759acbb9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Mon, 22 Jan 2018 13:34:40 -0500 Subject: [PATCH 19/36] 3448: Update comments for the fileHandler and variable names --- .../sleuthkit/autopsy/coreutils/Logger.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java index 710e7ff9a1..5153e613b2 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java @@ -30,7 +30,7 @@ import java.util.logging.LogRecord; import org.sleuthkit.autopsy.core.UserPreferences; /** - * Autopsy specialization of the Java Logger class with custom file handlers. + * Autopsy specialization of the Java Logger class with a custom file handler. * Note that the custom loggers are not obtained from the global log manager. */ public final class Logger extends java.util.logging.Logger { @@ -40,7 +40,7 @@ public final class Logger extends java.util.logging.Logger { private static final String LOG_FILE_NAME = "autopsy.log"; //NON-NLS private static final Map namesToLoggers = new HashMap<>(); private static final Handler consoleHandler = new java.util.logging.ConsoleHandler(); - private static FileHandler fileHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); + private static FileHandler logFileHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); /** * Creates a custom file handler with a custom message formatter that @@ -88,7 +88,7 @@ public final class Logger extends java.util.logging.Logger { */ synchronized public static void setLogDirectory(String directoryPath) { /* - * Create file handlers for the new directory and swap them into all of + * Create a file handler for the new directory and swap it into all of * the existing loggers using thread-safe Logger methods. The new * handlers are added before the old handlers so that no messages will * be lost, but this makes it possible for log messages to be written @@ -99,17 +99,17 @@ public final class Logger extends java.util.logging.Logger { FileHandler newFileHandler = createFileHandlerWithTraces(directoryPath); for (Logger logger : namesToLoggers.values()) { logger.addHandler(newFileHandler); - logger.removeHandler(fileHandler); + logger.removeHandler(logFileHandler); } /* - * Close the old file handlers and save references to the new handlers + * Close the old file handler and save reference to the new handler * so they can be added to any new loggers. This swap is why this method * and the two overloads of getLogger() are synchronized, serializing - * access to userFileHandler. + * access to logFileHandler. */ - fileHandler.close(); - fileHandler = newFileHandler; + logFileHandler.close(); + logFileHandler = newFileHandler; } /** @@ -141,7 +141,7 @@ public final class Logger extends java.util.logging.Logger { synchronized public static Logger getLogger(String name, String resourceBundleName) { if (!namesToLoggers.containsKey(name)) { Logger logger = new Logger(name, resourceBundleName); - logger.addHandler(fileHandler); + logger.addHandler(logFileHandler); namesToLoggers.put(name, logger); } return namesToLoggers.get(name); From eefc085bc1846719713cc5bd418ca785cd26e17e Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 22 Jan 2018 15:55:12 -0500 Subject: [PATCH 20/36] 3501-Remove status column from open multi-user case browser --- .../autopsy/casemodule/CaseBrowser.java | 74 ++++--------------- .../autopsy/casemodule/MultiUserNode.java | 41 ++++------ 2 files changed, 32 insertions(+), 83 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index b25a231e83..24aaf70d2e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -31,14 +31,12 @@ 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.ArrayList; import java.util.List; -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; @@ -59,7 +57,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider private final org.openide.explorer.view.OutlineView outlineView; private int originalPathColumnIndex = 0; private static final Logger LOGGER = Logger.getLogger(CaseBrowser.class.getName()); - private LoadCaseMapWorker tableWorker; + private LoadCaseListWorker tableWorker; @Override public ExplorerManager getExplorerManager() { @@ -76,7 +74,6 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider 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()); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); customize(); @@ -123,8 +120,8 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider } /** - * Get the path to the .aut file for the selected case. - * + * Get the path to the .aut file for the selected case. + * * @return the full path to the selected case's .aut file */ String getCasePath() { @@ -160,7 +157,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider //set the table to display text informing the user that the list is being retreived and disable case selection EmptyNode emptyNode = new EmptyNode(Bundle.CaseBrowser_caseListLoading_message()); em.setRootContext(emptyNode); - tableWorker = new LoadCaseMapWorker(); + tableWorker = new LoadCaseListWorker(); tableWorker.execute(); } @@ -192,13 +189,11 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider // End of variables declaration//GEN-END:variables /** - * Swingworker to fetch the updated map of cases and their status in a - * background thread + * Swingworker to fetch the updated List of cases in a background thread */ - private class LoadCaseMapWorker extends SwingWorker { + private class LoadCaseListWorker extends SwingWorker { - private static final String ALERT_FILE_NAME = "autoingest.alert"; - private Map cases; + private List cases; /** * Gets a list of the cases in the top level case folder @@ -207,8 +202,8 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider * * @throws CoordinationServiceException */ - private Map getCases() throws CoordinationService.CoordinationServiceException { - Map casesMap = new HashMap<>(); + private List getCases() throws CoordinationService.CoordinationServiceException { + List caseList = new ArrayList<>(); List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); for (String node : nodeList) { @@ -216,64 +211,27 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider File caseFolder = casePath.toFile(); if (caseFolder.exists()) { /* - * Search for '*.aut' and 'autoingest.alert' files. + * Search for '*.aut' 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; + try { + caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); } - } - 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; + return caseList; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index 9443ae35e9..fa125e6a32 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -25,8 +25,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; 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; @@ -49,7 +47,6 @@ 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(MultiUserNode.class.getName()); private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; @@ -57,31 +54,30 @@ final class MultiUserNode extends AbstractNode { /** * Provides a root node with children which each represent a case. * - * @param caseMap the map of cases and a boolean indicating if they have an - * alert + * @param caseList the list of CaseMetadata objects representing the cases */ - MultiUserNode(Map caseMap) { - super(Children.create(new MultiUserNodeChildren(caseMap), true)); + MultiUserNode(List caseList) { + super(Children.create(new MultiUserNodeChildren(caseList), true)); } - static class MultiUserNodeChildren extends ChildFactory> { + static class MultiUserNodeChildren extends ChildFactory { - private final Map caseMap; + private final List caseList; - MultiUserNodeChildren(Map caseMap) { - this.caseMap = caseMap; + MultiUserNodeChildren(List caseList) { + this.caseList = caseList; } @Override - protected boolean createKeys(List> list) { - if (caseMap != null && caseMap.size() > 0) { - list.addAll(caseMap.entrySet()); + protected boolean createKeys(List list) { + if (caseList != null && caseList.size() > 0) { + list.addAll(caseList); } return true; } @Override - protected Node createNodeForKey(Entry key) { + protected Node createNodeForKey(CaseMetadata key) { return new MultiUserCaseNode(key); } @@ -95,19 +91,17 @@ final class MultiUserNode extends AbstractNode { private final String caseName; private final String caseCreatedDate; private final String caseMetadataFilePath; - private final boolean caseHasAlert; private final Path caseLogFilePath; - MultiUserCaseNode(Entry multiUserCase) { + MultiUserCaseNode(CaseMetadata multiUserCase) { super(Children.LEAF); - caseName = multiUserCase.getKey().getCaseDisplayName(); - caseCreatedDate = multiUserCase.getKey().getCreatedDate(); - caseHasAlert = multiUserCase.getValue(); + caseName = multiUserCase.getCaseDisplayName(); + caseCreatedDate = multiUserCase.getCreatedDate(); super.setName(caseName); setName(caseName); setDisplayName(caseName); - caseMetadataFilePath = multiUserCase.getKey().getFilePath().toString(); - caseLogFilePath = Paths.get(multiUserCase.getKey().getCaseDirectory(), LOG_FILE_NAME); + caseMetadataFilePath = multiUserCase.getFilePath().toString(); + caseLogFilePath = Paths.get(multiUserCase.getCaseDirectory(), LOG_FILE_NAME); } /** @@ -119,7 +113,6 @@ final class MultiUserNode extends AbstractNode { return new OpenMultiUserCaseAction(caseMetadataFilePath); } - @Messages({"MultiUserNode.AlertColumn.text=Alert"}) //text to display when there is an alert present @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -132,8 +125,6 @@ final class MultiUserNode extends AbstractNode { caseName)); 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 ? Bundle.MultiUserNode_AlertColumn_text() : ""))); ss.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), caseMetadataFilePath)); return s; From 2416f7e31d02832f3f37a548752244c4560b6a17 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 22 Jan 2018 16:09:27 -0500 Subject: [PATCH 21/36] 3501 Open Single User Case button moved to center of the open multi-user case panel --- .../casemodule/MultiUserCasesPanel.form | 36 +++++++++++++++---- .../casemodule/MultiUserCasesPanel.java | 31 ++++++++++------ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form index 3f897d70c0..beb38e312e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form @@ -27,13 +27,13 @@ - - - - + + + + - + @@ -52,7 +52,7 @@ - + @@ -64,6 +64,15 @@
+ + + + + + + + +
@@ -74,6 +83,12 @@ + + + + + + @@ -84,6 +99,15 @@ + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index a2a2ec13ad..a5a1f32879 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -165,6 +165,9 @@ final class MultiUserCasesPanel extends JPanel{ org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpen.text")); // NOI18N bnOpen.setEnabled(false); + bnOpen.setMaximumSize(new java.awt.Dimension(80, 23)); + bnOpen.setMinimumSize(new java.awt.Dimension(80, 23)); + bnOpen.setPreferredSize(new java.awt.Dimension(80, 23)); bnOpen.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnOpenActionPerformed(evt); @@ -172,6 +175,8 @@ final class MultiUserCasesPanel extends JPanel{ }); org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpenSingleUserCase.text")); // NOI18N + bnOpenSingleUserCase.setMinimumSize(new java.awt.Dimension(156, 23)); + bnOpenSingleUserCase.setPreferredSize(new java.awt.Dimension(156, 23)); bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnOpenSingleUserCaseActionPerformed(evt); @@ -179,6 +184,9 @@ final class MultiUserCasesPanel extends JPanel{ }); org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.cancelButton.text")); // NOI18N + cancelButton.setMaximumSize(new java.awt.Dimension(80, 23)); + cancelButton.setMinimumSize(new java.awt.Dimension(80, 23)); + cancelButton.setPreferredSize(new java.awt.Dimension(80, 23)); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); @@ -196,15 +204,18 @@ final class MultiUserCasesPanel extends JPanel{ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(caseExplorerScrollPane) .addGroup(layout.createSequentialGroup() - .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) + .addComponent(searchLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 120, Short.MAX_VALUE) + .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(226, 226, 226) + .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(bnOpenSingleUserCase) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cancelButton))) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnOpen, cancelButton}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -212,11 +223,11 @@ final class MultiUserCasesPanel extends JPanel{ .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.BASELINE) - .addComponent(cancelButton) - .addComponent(bnOpen) - .addComponent(bnOpenSingleUserCase) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) ); }// //GEN-END:initComponents From 73241a637bc869905828634148bd8611c13cfd65 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 23 Jan 2018 11:14:12 -0500 Subject: [PATCH 22/36] Add side effect to FileTypeDetector.getMIMEType --- .../corecomponents/MediaViewVideoPanel.java | 2 +- .../autopsy/coreutils/ImageUtils.java | 2 +- .../MSOfficeEmbeddedContentExtractor.java | 2 +- .../SevenZipExtractor.java | 2 +- .../EncryptionDetectionFileIngestModule.java | 2 +- .../exif/ExifParserFileIngestModule.java | 2 +- .../FileExtMismatchIngestModule.java | 2 +- .../modules/filetypeid/FileTypeDetector.java | 18 ++++++++++++------ .../filetypeid/FileTypeIdIngestModule.java | 2 +- .../autopsy/imagegallery/FileTypeUtils.java | 4 ++-- .../KeywordSearchIngestModule.java | 2 +- .../netbeans/core/startup/Bundle.properties | 2 +- .../core/windows/view/ui/Bundle.properties | 2 +- 13 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index 07426a3a73..28efdac6ff 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -151,7 +151,7 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture if (AUDIO_EXTENSIONS.contains("." + extension) || getExtensionsList().contains("." + extension)) { SortedSet mimeTypes = new TreeSet<>(getMimeTypes()); try { - String mimeType = new FileTypeDetector().detectMIMEType(file); + String mimeType = new FileTypeDetector().getMIMEType(file); return mimeTypes.contains(mimeType); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex); diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index c7a5ce997f..206ebb3dea 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -263,7 +263,7 @@ public class ImageUtils { return true; } else { try { - String mimeType = getFileTypeDetector().detectMIMEType(file); + String mimeType = getFileTypeDetector().getMIMEType(file); if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) { return true; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java index 7fca6a896c..506fc5a6f3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java @@ -135,7 +135,7 @@ class MSOfficeEmbeddedContentExtractor { * supported. Else it returns false. */ boolean isContentExtractionSupported(AbstractFile abstractFile) { - String abstractFileMimeType = fileTypeDetector.detectMIMEType(abstractFile); + String abstractFileMimeType = fileTypeDetector.getMIMEType(abstractFile); for (SupportedExtractionFormats s : SupportedExtractionFormats.values()) { if (s.toString().equals(abstractFileMimeType)) { abstractFileExtractionFormat = s; diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index deeb2e9ecf..ee63ac7754 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -139,7 +139,7 @@ class SevenZipExtractor { * supported. Else it returns false. */ boolean isSevenZipExtractionSupported(AbstractFile abstractFile) { - String abstractFileMimeType = fileTypeDetector.detectMIMEType(abstractFile); + String abstractFileMimeType = fileTypeDetector.getMIMEType(abstractFile); for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) { if (s.toString().equals(abstractFileMimeType)) { return true; diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 0430ec7ce3..7dd8258263 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -187,7 +187,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /* * Qualify the MIME type. */ - String mimeType = fileTypeDetector.detectMIMEType(file); + String mimeType = fileTypeDetector.getMIMEType(file); if (mimeType.equals("application/octet-stream")) { possiblyEncrypted = true; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java index 86aae82126..7aa1224e18 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java @@ -250,7 +250,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule { * @return true if to be processed */ private boolean parsableFormat(AbstractFile f) { - String mimeType = fileTypeDetector.detectMIMEType(f); + String mimeType = fileTypeDetector.getMIMEType(f); return supportedMimeTypes.contains(mimeType); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java index 7d50f34f6a..672bf83d97 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java @@ -169,7 +169,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule { if (settings.skipFilesWithNoExtension() && currActualExt.isEmpty()) { return false; } - String currActualSigType = detector.detectMIMEType(abstractFile); + String currActualSigType = detector.getMIMEType(abstractFile); if (settings.getCheckType() != CHECK_TYPE.ALL) { if (settings.getCheckType() == CHECK_TYPE.NO_TEXT_FILES) { if (!currActualExt.isEmpty() && currActualSigType.equals("text/plain")) { //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index c742110f6b..76ce936202 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -171,14 +171,15 @@ public class FileTypeDetector { } /** - * Detects the MIME type of a file. + * Detects the MIME type of a file, then writes it the AbstractFile object + * representing the file and also returns it. * * @param file The file to test. * * @return A MIME type name. If file type could not be detected, or results * were uncertain, octet-stream is returned. */ - public String detectMIMEType(AbstractFile file) { + public String getMIMEType(AbstractFile file) { /* * Check to see if the file has already been typed. */ @@ -250,6 +251,11 @@ public class FileTypeDetector { } } + /* + * Documented side effect: write the result to the AbstractFile object. + */ + file.setMIMEType(mimeType); + return mimeType; } @@ -293,7 +299,7 @@ public class FileTypeDetector { attributes.add(setNameAttribute); BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, FileTypeIdModuleFactory.getModuleName(), fileType.getMimeType()); attributes.add(ruleNameAttribute); - artifact.addAttributes(attributes); + artifact.addAttributes(attributes); try { Case.getCurrentCase().getServices().getBlackboard().indexArtifact(artifact); } catch (Blackboard.BlackboardException ex) { @@ -393,7 +399,7 @@ public class FileTypeDetector { */ @Deprecated public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException { - String fileType = detectMIMEType(file); + String fileType = getMIMEType(file); file.setMIMEType(fileType); file.save(); return fileType; @@ -417,7 +423,7 @@ public class FileTypeDetector { */ @Deprecated public String getFileType(AbstractFile file) throws TskCoreException { - String fileType = detectMIMEType(file); + String fileType = getMIMEType(file); file.setMIMEType(fileType); file.save(); return fileType; @@ -437,7 +443,7 @@ public class FileTypeDetector { */ @Deprecated public String detect(AbstractFile file) throws TskCoreException { - String fileType = detectMIMEType(file); + String fileType = getMIMEType(file); return fileType; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java index 64650ed0c4..8e6b3a58b2 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java @@ -91,7 +91,7 @@ public class FileTypeIdIngestModule implements FileIngestModule { */ try { long startTime = System.currentTimeMillis(); - file.setMIMEType(fileTypeDetector.detectMIMEType(file)); + fileTypeDetector.getMIMEType(file); addToTotals(jobId, (System.currentTimeMillis() - startTime)); return ProcessResult.OK; } catch (Exception e) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java index 9e47579a46..91ce6d28f5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java @@ -220,7 +220,7 @@ public enum FileTypeUtils { * mimetype could not be detected. */ static boolean hasDrawableMIMEType(AbstractFile file) throws FileTypeDetector.FileTypeDetectorInitException { - String mimeType = getFileTypeDetector().detectMIMEType(file).toLowerCase(); + String mimeType = getFileTypeDetector().getMIMEType(file).toLowerCase(); return isDrawableMimeType(mimeType) || (mimeType.equals("audio/x-aiff") && "tiff".equalsIgnoreCase(file.getNameExtension())); } @@ -235,7 +235,7 @@ public enum FileTypeUtils { */ public static boolean hasVideoMIMEType(AbstractFile file) { try { - String mimeType = getFileTypeDetector().detectMIMEType(file).toLowerCase(); + String mimeType = getFileTypeDetector().getMIMEType(file).toLowerCase(); return mimeType.startsWith("video/") || videoMimeTypes.contains(mimeType); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { LOGGER.log(Level.SEVERE, "Error determining MIME type of " + getContentPathSafe(file), ex); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index ea45987a4d..2e0cd18c0e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -511,7 +511,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (context.fileIngestIsCancelled()) { return; } - String fileType = fileTypeDetector.detectMIMEType(aFile); + String fileType = fileTypeDetector.getMIMEType(aFile); // we skip archive formats that are opened by the archive module. // @@@ We could have a check here to see if the archive module was enabled though... diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index e0a4c85328..c890aff42c 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 05 Jan 2018 10:31:22 -0500 +#Tue, 23 Jan 2018 11:13:26 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 8f4ccbd194..5178eab0a3 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 05 Jan 2018 10:31:22 -0500 +#Tue, 23 Jan 2018 11:13:26 -0500 CTL_MainWindow_Title=Autopsy 4.5.0 CTL_MainWindow_Title_No_Project=Autopsy 4.5.0 From 23724fa4fc7809dd176eb10c6d652a7fea72a9c2 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 23 Jan 2018 11:16:54 -0500 Subject: [PATCH 23/36] Update docs comment for FileTypeDetector.getMIMEType --- .../sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index 76ce936202..b5ebcae990 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -172,7 +172,7 @@ public class FileTypeDetector { /** * Detects the MIME type of a file, then writes it the AbstractFile object - * representing the file and also returns it. + * representing the file and returns the detected type. * * @param file The file to test. * From 3af2bb74294fa0d42bdde773a2ce5adee15079e4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 23 Jan 2018 11:29:06 -0500 Subject: [PATCH 24/36] 3485 Open Case Folder moved to case menu, tools menu reordered --- Core/src/org/sleuthkit/autopsy/actions/Bundle.properties | 8 ++++---- .../sleuthkit/autopsy/actions/OpenOutputFolderAction.java | 8 ++++---- Core/src/org/sleuthkit/autopsy/core/layer.xml | 6 +++--- .../autoingest/AutoIngestDashboardOpenAction.java | 5 +++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index 1cbc4b70b6..84f64f3190 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -36,10 +36,10 @@ GetTagNameDialog.tagNameExistsTskCore.msg=The {0} tag name already exists in the OpenLogFolder.error1=Log File Not Found: {0} OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder CTL_OpenLogFolder=Open Log Folder -CTL_OpenOutputFolder=Open Output Folder -OpenOutputFolder.error1=Output Folder Not Found\: {0} -OpenOutputFolder.noCaseOpen=No open case, therefore no current output folder available. -OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder +CTL_OpenOutputFolder=Open Case Folder +OpenOutputFolder.error1=Case Output Folder Not Found\: {0} +OpenOutputFolder.noCaseOpen=No open case, therefore no current case output folder available. +OpenOutputFolder.CouldNotOpenOutputFolder=Could not open case output folder ShowIngestProgressSnapshotAction.actionName.text=Get Ingest Progress Snapshot OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index c3477859e6..24370b5a73 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; /** - * The action associated with the Tools/Open Output Folder menu item. It opens a + * The action associated with the Tools/Open Case Folder menu item. It opens a * file explorer window for the root output directory for the currently open * case. If the case is a single-user case, this is the case directory. If the * case is a multi-user case, this is a subdirectory of the case directory @@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * This action should only be invoked in the event dispatch thread (EDT). */ @ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = false) -@ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) +@ActionReference(path = "Menu/Case", position = 302) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help") public final class OpenOutputFolderAction extends CallableSystemAction { @@ -61,7 +61,7 @@ public final class OpenOutputFolderAction extends CallableSystemAction { try { Desktop.getDesktop().open(outputDir); } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Failed to open output folder %s", outputDir), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Failed to open case output folder %s", outputDir), ex); //NON-NLS NotifyDescriptor descriptor = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(descriptor); diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 465529fa9f..fa78fe5485 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -198,10 +198,10 @@ - + - + @@ -213,7 +213,7 @@ --> - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java index 45563a4f4b..f299fbc5a3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,13 +28,14 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboardOpenAction") -@ActionReference(path = "Menu/Tools", position = 104) +@ActionReference(path = "Menu/Tools", position = 201) @ActionRegistration(displayName = "#CTL_AutoIngestDashboardOpenAction", lazy = false) @Messages({"CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard"}) public final class AutoIngestDashboardOpenAction extends CallableSystemAction { private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboardOpenAction.class.getName()); private static final String DISPLAY_NAME = Bundle.CTL_AutoIngestDashboardOpenAction(); + private static final long serialVersionUID = 1L; @Override public boolean isEnabled() { From fb580e2bffb8ffa1471ac245ab1417df96c20ff7 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 23 Jan 2018 14:53:54 -0500 Subject: [PATCH 25/36] Added page documenting the Live Triage function --- docs/doxygen-user/images/live_triage_case.png | Bin 0 -> 26594 bytes .../images/live_triage_dialog.png | Bin 0 -> 17794 bytes docs/doxygen-user/images/live_triage_ds.png | Bin 0 -> 39635 bytes .../images/live_triage_script.png | Bin 0 -> 12839 bytes docs/doxygen-user/live_triage.dox | 33 ++++++++++++++++++ docs/doxygen-user/main.dox | 1 + 6 files changed, 34 insertions(+) create mode 100644 docs/doxygen-user/images/live_triage_case.png create mode 100644 docs/doxygen-user/images/live_triage_dialog.png create mode 100644 docs/doxygen-user/images/live_triage_ds.png create mode 100644 docs/doxygen-user/images/live_triage_script.png create mode 100644 docs/doxygen-user/live_triage.dox diff --git a/docs/doxygen-user/images/live_triage_case.png b/docs/doxygen-user/images/live_triage_case.png new file mode 100644 index 0000000000000000000000000000000000000000..fee49485f5cb23e3a5d06c5304fa7dff4c91f06c GIT binary patch literal 26594 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 S3b;TDxB%GI&t;ucLK6TvSzk^7 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/live_triage_ds.png b/docs/doxygen-user/images/live_triage_ds.png new file mode 100644 index 0000000000000000000000000000000000000000..f21a50aa82317547361a10f34ee02e5b34512221 GIT binary patch literal 39635 zcmaI71yqz#*9JP2C?MS}DJ@+BA`Q~rNJ=*&IfBw%(jYC}-AZ>O-Ce=}Lv!Ep`~JJu zzwW>8ti@tZ?K$s>y`R1J^Maj^#@!|13i$WJ zNmkbt1VYDy{~>@<(}{sfR5v+ADbx)FB0OBe%lasM5QrKiCn>J+dEsaoVxXb6vgtwn5FML6S#J~+X?kYCPtfc}e<$;l3N|{Nk%b`^=p#Y}{mbcklHxkn=3=?x zI@+|$75rNqm=*{mg1-+H;^ie^M1hyK9`9D4Jlx$=J`{91o7>nNe0zb%XZI&2Ia$DB z5I>y*-UnMSV|0{*Vs06h>uUd0nL1)hgV$H5uC5Nb-=x~z+M4$Li!us4AP97=zA6M= zrGhm3ID`?)%Rj{8F?{GB7?{85N{S#J#rfBT`*9@wM^tpPum5$NAbHgE^z=(IKJtUq zZ#78p_H4ygCWm?OrXoZ9G*wD+^5@2pC@6ffLD}v&;SmvZu*NG*ZNna7LJ++6%gKhU zzj7+i0G^Drl$6w);Rs}St%JeZB;HuF*mGJ0p!fsqY>qsN4txW-5y^0^3UGBd^6jQ` zdhATk1=|;VBXb}$Zl3D6zDRxuS0^9$^ zl>F%)@ZXg6zw3=%CMDp#;@}XmN)y>F&nm^1kQT?5jv%f&j9h&YpzgfPD!xW5y(R`< z8k3mTvkSZ-?cE|xvC~!4w8*EW+Y7w#`bP#gzMDpm@kd!_b#wErUxE#gi*@hdvZu^) z*`sDBQ5sWZvm>2g>%kd35;Iz9$v4B*@CPJjWsHUtr9LdHA5&d8Ru#{$`kq`Tb!{|* zptQ6!%_MW8H;HD`^A$Vv%;2L^UA+q^PAsiVM2Nu6WoXUrn2eC>OSP=6oy2VVjo$fdbzqOad<+uKct3|yL=}qzl}wm6e<OU+`q$J5slsezd3^K}NPt zw|;+C6)!64`&ev^Mq}#FUVC$~?3D`p^^Y1p~%rMhqw<(&}M!9^1kug|E_L*DLh1C<{Ht0*&-FZ3kG>dNQ#>yn%r zv3A{+|D`3(_F$cQ=<5I-$r#CsnAt>^V~XO#xu@Li6f(zbtjuWdD-KD?+P{f&l=(>x z`U{ehVquH*@M6Ycn|pcTYfYPj)O`Jh)hbaL!i8q$calY8i{mg>Vg4T@SDTG@mn6FC2oU!zFj%!P^1-Vg%(01pjTQ?S;Cad z`Zt|QmsvvmA6|JiTn`*P4c~V0+=s%~>fLqDgK-Ar(YLbfOtP;}>26(_l4-Hp=!0&( z=`nbWJp9QD+68&W=Gyyn0yy00>(RE{y`(k}@bzZYP;P|~v?3!FdH2)qK>U>S{nfi} z#=bFQ=fJ6}EM>m?FY1a;01g8h93JtU@f+dhMm3LdOmw2M3XI|_M2?uAyYdoYwY$BR>&>wc45EiPqnS_ z0fz5W3w_?!ap+H?xiJ)I9iiA~EC^g7j2N6;X;*ElJYkbpO5N8jxr|S?TXYZT${E67 zrKKN`b*AG3E1j(n{ZYRUll05w0x_;xjVwz7Tz~SH6&>5J24wZlOW630IP2H>2c;#Z zo>m~I2@O;w{ZY5;1!l*m^BWbELmjF2`;ZRY5B8ke-3Gk-dh;!?)~5Z3uRM(2@xm5! zsLI1>J;~E$j8fksI0?o5acRx_Xq0cp7DrHVT?(p0o?36a$M%>u*LB@~vRB9+rG zRL-0}FJw2=ZFId6_D1*X4hd;8btHUcxKF!Sao5Be>)EDI%=7>Zs;T)4ehEA%QbZ&% zj-W@nF&Crh2o9c@iMFVA_0IhbRPsXStan#_7JI^#-j!_-@V? zb<{@KuF2m0tg+lRcdR-%iFNsGAuH3yBtt`8ZsXWbY z0<+&{Z7Y9Xv0Iu+zHqxyP%xNFpz?yQFY2#6Y%3Q3v9=Efx3;*Ygo%3EE6`3%J}2a) zSIL19NPdu?SHJq|K6N4EU@F;HiW;8~PZBQBynAZzR`PkGQP}EB*-3NT?M|J-N#=XW z;FZ;7A#PqiQ|2`3Lp``?=A7ILt+~hR$39);9;3AB+blCyQ|4Ibq!O)a!w(q{+sWpG zIf&QP!<3=F^%2jx5*%C*6VpmZQVKe>3Q!L(v54d1Ox#&j_*iCK{R%{vyqF%lxkO$xQ2dm|0dEG1NsrQ4% zFgEQmW24(mZ?r-_T>4L0N`?F+;U=smIjrOPv)a{#?e8TM?#Hik!QtE#GbmVXUbbCq z%IcHRQ}ME_GVZ4v?(^!#O|S-GO4#ZFvoZzP?P{9!?53?_`RpC5>e2bJvY1C!axx%+ zVjv>Kl!!giMhf;QkkS1^yu3JuT_u( z3-*jIDk6+j@R4i&KqQmJ-m=D3`N04~=F?TOt84EWPGPH>bQIaCdlO&MGd&qeslex$ zElU%*zt?3I6@5?JU@P~1eF!yrPY>&4FDu3#*T2SdkU?SgiK}f)m%{u@7fL;w-0dq= zoHQF6N!a{?F0hoe6TJe)6S^QWJ2=9`h*L~mtk)EQ&9?{nF)N2Lb?`F!I-i)8V4NZk zU$k&0xo?fy0zmjth)ik&4B_(oB^<;bEtKIp1rcCl_YWyI*0>%Z>%eCCAV}%lWGHa? zfQm+JmM){x@BaE9*n~($k&p>`kUN`xXpW3zsds&;?K(>AJf?^>z*ypa<^mow-%75U zuDPWB`VC@$U7jNJ;7`vI8se&CK%FC}Uf}CJQ{hYd2dA;-A2eHQvfF6D{3H5kbldiz ze}sz4nxuHIRk>DKf_nJa@1Xj%T^e|*FWH;rytWN*?=A*5jjtT5HY*yrprM6dVgKMR zA}0;dr?Zl4+j~IF_jp8nrD*EOk8jWRA6R~~A^V>E1<(N`!qC6j4@dO~-1*CIaIDa7 zCqnTUop6DsprnW3vAaBhCVY_%G+TJ*y_emw@c&-~;O!u6nSUsKLl$0m_aAxxKTRS3 zkoo^-)VRos1~CN%h2fuX>cDEgvD=iD5RF$oWGm0}K~j2iA@t5oj-}>Ox$2}d9*W%( zuEbK~Y4yA?MqHE-z6jLSQ~~`F@`EM21f;sf1XhGz6q3E>+NutbxjgPF;B@`X*NWTq z)fZbETg0xBhFODuoJ8OQ#V6(-Wy6aiLo{BTtyEb^)8nXG1hr_ZtymS!>WB2_Xy3V; zJsWblSTRWk;X!=>E4{YMW1992z^4<9Nqr3f2JTj>IS+L(EwHYDcX zxYM7^dJ$9)Rr9ia2Afry#S*c$ONmOK?*GMy<|odlqV*s@{l@Us16C}^jl4)!mWIYG zR*mw;n)v`45^{bZxZC>PY@&plSyPC6H+^n5w(3)r@H>tD;q)|jX3mKmZ7Yqqgm|cB z8RC;fQmT#x4Ne_Cqj#V#@PtRj32|D%zdu$C&gMMvx z*SptrHXZSd}va$QWPXL-CHCybLgET zmrtA$sxuaSqC$=ra5idzHO06Qm?TheVZUO*txFm9=W6mM=@A|Lg2Gfga`J=6L>4Vc z^7Vk4LXT4WY#cs~4L{Yoq@-zITDN2G$Gx(3S1jqp!O6IA{ccl{t{=wOBtp?oRX$5o zu~tvcXa0gtz_AI(m1)*dl%|oFSGko8p8Nwt@5*^EVNZ{Dhl5_!61>Yn8_6Bkbp$9BH)wYfaQqqZ|IRKqtdG zNTx%yRH>btTdlxy6;I;CJpnzFn)pWG+ebul)&40!9lRM&{=FU&6}6Lodi&S4B8nuZ zmW%;&EtAcwj+k}BCRL&Lf2$XRkCj~{b&j*h!N2|Q=Km=Y$Nt$@uot|jUcVCGFAmpT zW5H{Q4|E!w%}>LQp(0{yWM5%@Gn5k(69Vx-)7Rd0r;FP*-$+_PcKpCs&^Ziv37d$wOuTegB2Uz*!DQIv^2DntjvqJGi9){T7J8;1a{-q=Of} zVbFe!ur*Wsr{A8x{2ze<|LyzkW7*x;cmENFN%vo2ZT`+cv~QmjW}}tJ<5f zX|o8)vX&4vi{Sk3*lA1wH2av*41iQ~7^BQ1bWVTEO8BkPz701Tq4Q-M=HEylST=5X z?m@}5;AS~0STTqg9u*H0>AO3!lh#R4T3yB zC7=dKvdk!^ve}F3-(ozK zqg`@o8E#ZIdkD3-On^lP33O76kv?-vMegiw>2$4_b>+e^D^Gal-jtr0Wmvuuz}S-C7izaAUy7Qe^a06hdYRJ6-$^y1=h ze%I4Wl{XSeu(se+uE>N4?J5SAw-0N!ux_LDu(9NxoA{EwPKv@Og1Cf$9JV*l}CZ5l-3Ibe5J+;cF7cDsJLVdj> zI4xo)_DH)gl^dot;~nY>h+2kmk>n)oEsVYgg~%qx7dhGI@J*FdyJMGR$9WX@539-{ zSpQj~@Fq!|wygfv#@nfTUM}t1x!iOfGuBj%EY=BfKUsVg%*frqEX=^rTq%cE^EsYx zzO~l@*omvz(uDvQPDH431KRiV3%lYfVRFpi-&NNBIX_zI==1Yt92^Y@ciF`pbaRut zjN)}#EgBr3_ovkk^n~(y1pl7Uo6Ia*9%)iEva@DJ??47jQkd|vo|a0wj#&dBN?FD> zSjXdcwZMfIA^>gBY1{hhf{_(bb}v0m-)?2<_6zP3kW~MC?c~LQB&SSF@&+;fRAH(w zwbgbjMmZ4cNY;6nE28D);2YBH)>%4_0iQrA+ub z-1N=Z5=)a;6>l;}e;qN~@ZTxv*QPA0=e;+bs!=OGNG)}|!9UawSTlSdT!_r!JSbT_ z7&XD?oC)kLY1hwu0YP9@%9Sl5LUfj@rDbRlVk98!t69!ex(ZBhwci{BrIK`clb7CTJ!+9p5 z*2)&VX$G9Hphgo_Q?aG7-W+Z2e78thC!bGx=&o2~xEMP^py1spK>mYdf&6A=MHq=SToj;4Nq;>YN?o?Lg5l#y{AeX9;z>8{V+sBy98Iv?oj7wr7`PR7&6DQ`XgUtHSrmPpIW%4L4iT z`kr|tch{%YZYRj%_&VhHby8u2SK-#|9|d(?^^a~I1VG2;LfJ4T$%MExh2z2VAjC6y z8wVBz4(d}=SKFgojU7$@B~ouo!-&>zT-v@%+UC%L_ng6RCFb?Rh${MvT(tSx#1|-z zXM4>X!kGr@bjy-i05~r(^!R(XFN*Y)vgg5Yzi$nL06C^Ex#zfW)LXDzcWjW4RvNuN zH@WjxPZZoZn)LU?=C*D2PpOf67OfiVt9Oay)}YzDazDb+2B;M|9M{5`?%5Q)&?y#W zzT6X=WE1_*G`iu*J^OGRd`E+cdvp94kpHg>NHTW0Ok%W^l88eO<4|-AUin7W**xYo zr|BonDK%|HPfrcQ;D095VLV!kaO;XU>d0d`E1XhB@1QtRIya6Wlta^<(4OJ(kVDFr z${6VRCc*dNaxQ?{!0%>TfB+=XQFNt%s#y72)h}a2igLcZgl{JlX%;Z3Z_V5@bYs^t znXQXT_;6z(@caP?qqI|X16vPPnCe!tj5M2C`qu1HDZC&@v`BV<*eqO8j+sWf{lD{9 zh0p&vVC$Q7S-iKK&+2jlplB67q#Z!|fLJo!H68hjLUGZ?;{sgpc!@yu-JzWmtcQ)@w9a1fyF4Vg9H4#mbG&nSyF}1aVEn1HpQ?kHlh#A7(kEO6N>MG=g zKkY#E#pG!l2)jKtmUc9=-cd0$j2Q#IK@xyxsZ96a%M43gM1e?!dwjv)%`L$vpWGEbREEo9H0qrGt!Xm-auL zIw?4&0tVU31?o7O<#YOgf5^S6_Hrd~8g+2$xT=4Zu1$(h`qYzoNBxj3$fv@7g#gW6 zFX+ZtB+0oWr!mp7!TzmE^?)6FhH&9D&GYYy4bh>bybLze$%6TW;)6`Bnr|2mu0{yO zwE|pb@a9Ac9Ak*~JRPTx)7Sz`n1DehH|eToInr#9p;lcl8mCbdg?$5EB}^x4S{iZe zXE{YK%?_5OIvNY#iKdVh_Ll_nEeoe9d-~2Bka^_3Q1Z?^9z??dkxCYl95|%5oy&=w zn1KnCz)@=xGm_QIT{>UhiqP@<-r_omQ^_Bq*ZHaLa&F#IzL|xaoO?fO8L_oKrR6`N zC^MGyw!}od13W`QiT3LL*MurrGQBhw;Eb%9aBk+%kUI(-EML9$t_|GsMM$Lt27g%^ zmETeG!jzSAmY}wGTY_x;2(vE>;HEJM`w zv^F;6(~}Ihv8Z>k$9{<}R_yiG{0QNC8G#&XmVoaSyxRid!%g;xmO&BP8<1jR)kUC! z@c=0q?^W!xQ=^z`0`H!J<^)SegSsCGinwbu&vEImrrh}m+w^qJ>1Z^SEGzmPc?r_P zt4QUP!0{ikJ8Z@smlm^GcemfLfb7Ttp?kp9xfFmfl7Z_8wW)-2;qFyK4iDwj_;y_4K z8Z9i5oM9%zjPeQ5w%$^4BCi)?PSNtu5AK9ss;IB;Z_?UuQ_#@3Ty}@v-{MI)9*ZCC zkwy^anpWv3zaf6@f|=3>(}}vQeZPEpd^P8fCD={@twt`{&z~RO>6VT@n@vc%(L8_O(W@*C4oWR59EOG<4;6UIEfV>D-kApai!BK(x~rza zC=DIc+s=#&N7IFTuYiU^B}cjS1vu=TetvoTWx4a2Pn%#XlCFU{9bg=^UY!^a>OU(p*?TM6cOFxUrMAK^ zCpm42=3IRr9TNPOZB-+kzqIw^h`Vwr_U^Ll;SPa*X+*xGsQX&eJnHb%E1EE0^HVan zu~s&JZ>vp8rh>j%*`$Qg_R~=T8k)3^caP(SOm}m33xUSW5p8{*^5=Nt9MB zO(YPG7xQ>%tWw?VGkn^W;^64eI(R9!Y>;;S=Mq}4gZSa8uvXELm3{-iVWa9W&lHMj zB&NSYv7eZQmv4VN=SpP}Ny5)(v-IbuGuW>BdGZ`qk_QzkmP>{rRhmud&PwzN4%xyr zS^cX1jVw714$-%u$-b+pl8u7xNFRh$x_8m32Vb7W4gVL0Vfp!}Wj_5SvYK>A4grz> z3sE+Y)HJo)C{9$P6E!_p8nog zYe&WwZb1fNBdKdn0zRh|kwYtRd--mZg8nu_M`jSJa(ZfmG!FR;F*1DydRUP(Oq%&o zgoqJbu%H~92Tths-IVPeCwn2m7}32-(-~u*UH+oyQ!(gwcF~m;{=T+Ro-sRIAgBX7 z7A2ZC7>vQRau)cy;*qQ_#`N_+$OtvAF*-njm7K0O?T7 zV2KEwvL_KH2+#dt4JG?$-7Fc;OJAKg>V@acV^m428ZjZ=zea=-SW2X1FdoBV!7@#YS=$4j*%Rrk+DW8e{!bfN*$0X zG_TYKCf><**T;kQ=-|!Z1R1~859={Q2x>3P@*`zL0PxEUP<61E76kF#!J{Abd}84Y zir9;$)@;Qxwbxpe_A7~$d(ocZ7vxhF9_Lz9&L+`{7g^PxK#x+Mcz8*8#?EPKa&epf znugswNqc%*g1PX$#J~6ee%Q7K2d1AD@u^|jgGcv67?=J=6^>J7qjcw@*mLW_;7=c?k*89Dz5fxty6`?8J!e$=*z=xlZKiq=eHDE4MoM6I0al%za$9|$%s zvl)m|{&XGV{wODGYv%9JU1|`aS_sB^gF$kh@+W?6*~hSKWab)?cS+@?x>2g03mV?ecun~w7udXTACuPcvt_YuV$P0<5q5+Yqg%5-kgV$=G(zaDgpVnD$ zmnQ0W-yu@!CG(P0o5zbTa@{{>;xFuE8sC`rk5Oj{;)2GN#kaf$)tkh6&1-fvU!0lu zTd8es`n{Y3B2V=*>OtUS>xq)Y8Nrj#Ex`hznz7*DUTr8=R3<8bgCpXcw|RjA+w9&) z|Mhy*hqvQWaWV3VhQwO4llQ%$y^6RNk#`)8$dq;$enP&Gxzei}6_d?3* zU}KO^y=d1`Y2Dy=lq=)VxkQEaxlc$dYF#x295_Yn%bFetUXvQM(lQmS{l^Ng?y$08 zO)XbJgaQ@updtWZLW0`8a0C7S#FU+Ojux zgMeP7<&)*7)4j>ZVy?Oy;~|a*)Y93y8Pa~0#qU52>{7+edq5)u5#a}_dHk!M%Q=IG zuR%m%{Sf=mFRt=S7Rm0+p1&_XplW|gLn?DCkd^)AC?tlV!?^r%(y3z5v-5_mR&KR;diF~eM{fl7zS=z^qS!;&k3%aOg1|jzH^apj zARsN)N@AGgSatbgM{VM1wr+Uo`g7|t8+ zd;(Z-Zx%E9&~7Pw{7O*o$X;;0LYZubdA1sHCV3bh3|}jQY{=fw)cT^+9u_lkM0XG*}frkuN z29F}ylN&&z82cU{TJ^4t!%#fRd$Z1_5oko zLH!f%IoIr*ZmnM6=Q0NoM-MiUkq5N^H99&H+cHT~d%z^(Pq&!>hr4#sZ6-4`v0dNr zbk2O+!n{&`zPc&XQ2)ZlqTRm&v)0%w)|zdXV5@00Nh9x7R?2Kx1*_MksV{@2$FnMj zWubIrxMjQE>5Fxrk7=e>M{gp?^gb_}St4ILqfZYm}Q`q4wtOUlElhK-M#8ox8(rx2dy-$~_@KAQWFz)5M&{7uA z&C=G@Uxmd}jQ)3pxBqsjzmD8GJhWuk2t)gQ(hE`WgV_#T-$;Yj(cP8Q{+nw1Y z)>fBk`@syc5giDkfB9l>94njK8t4Oo4o_W@Y8ONe>DUyBYThadwpjRHe-Wt0DsCez z9~tDg+jMWFxgf0L+|mewp^ZULG2ehaWE5aZ)~$!1DCEGm;VHHKP&SC$t4Z z`c?NW-bXRt3&yOFNr@)W?#;2j-z}vs7}U(PeLg8zW=Ok1 zhTnKE`RzNAoB`mR0SerqPTIhL>37?QHb>)-f?w_=+G-P~l5wDiu0maF=3&7Zc`)t8 z@q3;vAYak=PP!5u%g_wXrY&U)%!GbDgbmNAe`7G>pt@$Ut1B;?eHXOAr>!il6V7rI z^%LP;R#R%?uR8K{-NJM0Cc+1=B5hTVG0s>kHq5|!!SVNYC(bY6$$MPRmqOIdGwr0DoQz%(p9(Jx0Tbjxy8Hj#rC22DB` z5!NsmE5ylL?dYMPw44>ta2J;K*M1K-mPtjcQ7SW^q_+@7-|Sbw%c;sr{E%0QsSF z?FD!_>%SVUk%Okw$+}nvh6>k8LFE67oA3<1yvF~P)pE1DzzYxn(g9w=kG?maR3N2^~(lU^2!_zLL*sRMuH!kz28N1=$-2>4I2Pf)ArW^6XAd-$rw zs+BchlbnkbUCOn?7I<4#Bsbc;*}Qm%et6B<*B=(m!VaZt^klkDe>?|?V9?B|XAVC6 znz=QH)eN}S$#i%x=_txwo_s&KgpS_1cBY#ZbH{%UXW4&TJ|}ZS0s-U;UCG9g2lw2f zH-|r)M?XkzwZEz;$oX|Q8x*u4pXkQF+H5nKB9fM03M4?LRYY2yli}; zb_$L;&k&SB!O)%9m;;^wbBtyHT?m92d{|uajY!9+OYEh|e`<8KkdBG9)zx*Mp|iby z%tE38pOM&aTqD7SJ`ev-s>+_S_e*b_7hJjavGN=Z0_kPy}P)Sf@5kyh%zm6@)*M9sG{t{N>pDmkh z&lj&Z5Z0aKdD*HfA(vd~v# z+D-%^u%B1R1pmgNpcoNcyoHryJJo%Ee%Y1z_PX_@lEI*}3 zIhvp2A2b1?_WAEM!^zFn2IA-WF#suuo5nMtF`LC^we$sMFw*A2-;wNbhp9S?Z|-7g z5q!&h$Z(dBZ-BG?wWUx)ofL6rBdQ@iUv4qJ)lk0T zTJ!B-0~D0prB9t)<_N5xhalTQ6yuE~$+_=S-ZB${pP*+0!7A%{*?KMOKZUoKCa0TW zMtHiQpT*OC@?!`|OBskFop3GHuP_qw0QaoR-@&i-E^M^JRtkT2H<0D^%|I(tBTgdKh;{Xg1_vPgAYJ{h*Y1+8?Hp$l~wA#Hn?VYmA`|n)9OsiXvUe^-nS?qmBfnNl~ ziGNA&Xfw}dPQX=R7dF%X#RtC&+=TJh=4~siFJA>84e$V#E$=aA@Y0JL0J7zZE&<=b3G!I@V(0)u^ZJs<4@>vcstDGt(zO!oM+*cDG_G?HD5^T(BS z@k`Ms8n`FD(-wF$vHkwL#_ib$z$HK2<}Q5iLBh4KuZVVb@OwUd#{9_aax2vLNVR*7 z0z-RajBEanziwgN7$o8BI3j^m$vvaz((lPKs@)5|RUZMK#>p$Q@FK-~W)}c29TZD^ z^Iggro6`^BAy_I^uS>ziX=?WlXjhF-Wc2mFIrC{~l9!cNK`zbQC9AHV6z*w>c!$bD zwtmHdWg^SH)xtm$J|c0I)Ycf%tz|n=vR(5I5iD(^b@+PP3&oEO9d5LdBUS)&%ateh z?LUC(eG8vlDns3$1biE`TpItp2za#=IskRmbh}pi0dL@x*-&?j!gdgKKe<|{&mjAS zib&kpMvhn3Vdb`Sv5%kmmltvE#yubjL!W?Y(gff#5RkfX+~t;za`K}RH`VVE-MLGL z*=sMdQsEY+4?E})f-n&NC&~u)y%yvgdp61hBs&Gf$D@_f1t#n>N47PAyqG(Vu(pmp zVa49={ylJzt@RdttNwX>5gZD{m`=u$U)KM(cV{Je{@PhWd%9g0j~o;AmKMRBg~>|A zSL(?auNd1~s--PUQ98|T8F+!}OEx=hA?TfYmuJtH6LEl5vT(xU{{$q{h?T~v{&5uB z{2yhBU1rPlgX2M83ef)Hr_#zwY(^l5`NJ4+T1gv!{g-|N9DcakaQ>I11QZ3l0PHUO z^M5pb$$ohl^E-z72TK8*$+#hVZKn_?J%mzetY%74@H{cc&)HRKN&g zClEvt}peXH)uCG(*Nqx`i22 zeJ)p2qGKUjsvG}tyn*!a1)Rg#*}3Vqef@K+L-Z>*rvQTy-wrJ6<>$4i!#a4D82gv( z?NP~;59s;JAx0xU%MA-Nzgg;UcZ7#K5jSb}0dJ@;_wCmo^h?Itg+!ju@w|dB`I)B5%SyJU>?@2(V{Fcn>Tq) zmgGOa>MAng*#PIJplURgvwZ$xlZmhM$Kh+BhOuwBqSnQqzr5+@3^HEvv>P_Wu<8f# zS_n@J1R6{Kj5H}LDNU>LL8_DE9!*hk7#H%}K78=34GsSJrSvC3i{P##qN9(7Jjv51 zJXpfff_5cpkJx#ycL$G1$BZTLGq>Rs>34HscrO%Lx^r6VT~>0Te1ev(?~p{RBmeWb zl!df?%4q4oys%^MJg3qijh;I^N=n(zBO|EVzBiY#n~Yc;V8`X^G76urn$rian;qkm zyYs`}6)SwVZG9tPU*E0k?}9!_%1R}dJ--<7AkghOnW(J$5iq6)u4;Jl&DQ!H)&1|@ zGCqIQKXtWJeTfwiV901x6z6 zm;GmzrlV7%=%*rryMuk9A#wbzXS>0DS zDCG!P*^IaHXp`sJ&hO?!CBy4`{i^daw}W51B7*A!>J^{2H(ik)R>S_`7-FaNDevHR z>|Z@hHz}#3WBONbT_p~?ey@a{QrvE|Nf@_+_q3TuQT*|o3mY}PaH$JZcZjHNbMm=2 zkFB*Cl-Ydxm_A!vy~TQn!*kBUP|}}|6Rd+?`8=*Vwa<q98&9-IRaQ*c}P-qmA%Fw1j*;jd@({8dHs*=mN&K)mjw^ixuG4krKc^9EF>f@ljpl}jcqhb&0Kr8;S=;} zySt2@CMT=a#8+HvTG^;_lptjL?J1MbddZVvX*%7Ga`O|TTz5uys7?%`qu%_*>YbI7 z6(b|fZf$3syyp&<_okKDuzMAKX4w-zz*?B>x&|*6QoX!KmRlmid4_co{v70FJve=O zw;DVJEs%T|xn%O4jHXL&HB zsRd{&kkK7kESg|qwPhLvnn3Tu#q3e0Eo8Jsc&z3fV(JE z9oc_#Rs0SH6wc)$kZ%C}q5b!P=;# zeJmi`g|2rx_~kW#zWx%vlbAK?TX}y>QSp$tX@Ef}?WUOkO$_ivY9{(;3*W5yiF6{o zG{8^(kA3|=EaU$+nm@pmCs-g1_ZE+iuYhqJw?*Gs4FYr&yCI}5`e|NdV9tP(vJeqO z{eg-~?52dGtf4QRywMP_zh6GCO?W%r&VF-d9DM<>X+R%S@kJhis)Y_P2e2ZI-C{5M z=5ZtuK^NVj=)dyzoRON*fl0n0cfxftHUii!kk2##-k%h*v)D(`SW&_5`%}5NFI9A3 z`ACP~c;VdNbh$l!`ZhxcY^QLmQeArt}g>)$eTtO^&6IGa*!9STXk};nlv$lm#N_lD5 zR`zS~s~y`PHm4{2_8<5CSBF`ni*|UeE3chg&}PFtX&J{Bw>p^agQuFUKPt=beJ<(s zJlri|C)*Ys&is!31M?-yNv@L~Kih?{LOT}C%A)VOKPu(JCGnoJ2jDkV#;Hv%04qW4 z$Q&WZ@zkfKSI*J0 z3c^`kLMM68J^$#;qDodB(!NIfse)(+IZJIeo3}9T-bQ=%QRV#`>7w9L1wOS=L885E z=Du0M@=@-aS>F<_z*N@^bRDbtW2hFNn}a+|c^w*_>BU}E>fBRUAXzKxK;~>=AP-o zUTn?C5pHO=C=;#a3^_vP2lq#;eEx?Ekn#R&_Ca>-4V_bJs>f>NF+&NIEek)bQ?O@j zqH*X&i7;CIT#?If!{T&sC#OtJ_&c*2_hrQk`#pl5F^ur4h3I zxi~LYS%YU0ox!Pl|@0j(W%`WRCl3N5ZxoV(FTD z{|D)$hry}PeJW=t=@_R`y|TU4WIpqqNK!-j=f}0)5x&C$MNKTWC+l>u_0ZLGBlnff ztw~BSTf8Q%3W z8b7|7b7pszffQTM&WD!Y>y-eX)tT$%m(A(bg>`(3JR zU<1q0@m1Bxo z>`Cp5B_`}T@{nb(6O&7c3JVULZbmYG{R~F}Mk)i3`}BFg$2D)*43S;^i*!B8x+V8f zJC2q_;8jbMA(nkG^uX2f&a>v>{`SVsrpvfz7qz5fwX#Wd!Eq{Tj&PN>T`IZZlHL!7 z*V6b|KqiSF&`gzU2uT$hr`O7r|Df&KqJUOJL zDvE7MNj_I$P0b#SCa!G{+@6KO$#~!`ns4~|_CYkUVKrty0V*^b8}J_1?!yX=lup&) zi3>C0>2$AcQJ#;VGA3ibd0;Xmh>O18zdO(m9Vg#BzpwfBlXdRR&m35xHnV(6TuwqJ zas>hK-h)*`QtiQ}&=5eyMv7J(lV>Wo)hmB~IKJ_nSLl17Ze5L%-w{4zj`G-)AZ}E~ z#yzXpZl1F8nQXP)ShJrki|Tpr>fU~Oyr@VW^^FWz%!k|JamNl|mzmbbMl3m!41|P4 zwe##kX4@1bL&d}@FV|}Wa(B@}1RTc=Tg<19y=$yWrzs5OB>R-krn;lEHu{L2*+pQ= zg1sTiY7CUuu*Z1xg2rcko>t44yE%*uXJ+w)mh}lo*PnWPyT|A`^#z7Lv8DLn z<@4U56ma|JqoaNbumn9bQkqd+xpGz4PXe(t#?k?SRxTg1 zuH%Pu9#&hzRprSsL>&TV7=Z4O?`cKW+P8$w~lZ9K1v+dHV1d(+UyRj z5W&#LvIQS2fs}^3H+{DUiOcAQ$XWK&umcWAEoy}LE0}SPUl(>*tUD;&biO?$+ZUm2 zo$iC6Wp08__VRtw620-2KO#f*wgft+r({y|R9##j=Tt&y?$4*s*KG zLL&F)78jfD1IqPi+7#x^hEflpUw&PGjpf(8^$KKtvT#G%o*E|2FqP$Dug`07o$hDB zH)1&NcA;k~>gsq_cbRD%XvH!;&%3GIypPsExZ=u_d0$tv{PTN?pc?lE*+e@+mU~}! z`6bt(ia*9fF^}nJG*BO@{QlhCOlnZU=GfuZ)U%lYR;Rz~Dl-c-`2@1B z=mop)+qK@MZr!}wEWlUWRQGtHl;}QyY2al+c|h%NdVc1fe}ur^5@U)Za>Euk7X3pm zSz4%I&$&NeaXGXFi%j?0=_T%TNUT4Y_e%EJ{&i}4HE304qmS6sU|C94o}QP|_wqc- zNh@fEQkjd@x^T#b?86LNPo6Im+RS~~UEjV8`>IGs?qNk=)sVKI<6KAJ7I&sAPgc>Wqq>H$j+H;6MY2|2?nzGJW=p;C`F zw5^U-RFM9|)Il9Rrxe)(<-Q?BsvH5jg>PvZRkRpuf3Erg-et~zY#G|`2Tn)j{|wcAl|eyqI*0>>vcK`wBP5jS(zeq%vd&WE zsAR>j&~=Y~l8eir^o#bSLf(|~Sg(1hkw#+triy2EbEf3@MQPN1KCPYQt-&tXX(Taa zeR6=oVB$V(YQ^cGAWK*5sm&e=3UZs2)I*^@ACA~vZUA;CLt>}6x*XD{qi7T*L z{^K$hr^^T)yo6ATwej8uE$sdV&MiR~zjm$3>Rsg5SFRXm8b`wc*Ri(s-}?@p@}c)Q z%P(?X&(YJjs!7_lZt@YLR{v;pwk-G!;OkzRbecUs^uJ)F_3|Z1I-Hv(cIBkuGk`gi zbK>gVFthdYbNnu}X6@w<$T&#aUTfGTF--PDrZXSq7c9==_?5_x?+P~^nNd)F@a$}l zX3Z-9^IKxZK}FMb0iuHZ3s4b_Rlw0MRAaVzU99!@XYh7Hp_uhz@G+2xbz+WuVxt6d z&X;a}En`?frTw!hs=spRKcwe>s>c4W)b2n1^zSv?-e~93LCf2d2KPegw(=CdSewWS z2PfK(NiD&Y7#Z0|`AI={Dvn?s?*|F|cwL-R*Y8qam{gZQ7-8`8ES1G`)Te7Q!4h;2 z01=4@xKf$Z$dVsL^BLd4?X|y&d`UnD~^D}m^l#F=YTThJ)lnZ-AH64 zP~0}wZ~J{DWOu?V;|;sL3`>pE@GY05v~*#@>{UtZt@A6O2igISxO1^it)L+*Dx8ed z*>tTahDPk}!X(R<)m}$}#gF^CLuha>KABiPoXy6T)}Cc&v{`4q8V=v*0_^%K3la%U17247ddNl^H1Q2sd)jNA zq-h?P^y`!z=lT*TGA=@v&|TG2E@akEE=+D3NE|%UsefhhE$vTTc4D=r>ECK4SC@Jp zRayQ^f)Ya}d6(PvKLtO2*K(1b_r3pDva{N(DD-K}io-Ux{xZ}0#0*HoBfrXXd=4iP z}`tj zOHXDlQySUo(6ebO|FAGc*{|1$cCDaI0mW;3^we{DS_#bsAN&2d$z8GoO!o!!Gc z4vd`AJ=%T+IOtuWHo1m{iO8mFfBczTC5p=}RSzJwM=z@sd@ml(_UM3(Hf+s5+pd2+ z4nHF!v#3!S+_g6U9BKEKYc@h>Hci2{I*R@ahjz4#MP@IKAT2}vI0e<4Gu$W>Cl&s8 zKVeYs^Qdik4L7%ObT+4J|4ib-t?~h|1OAED0ob9HWV&QwX?hGXFLxD2qcZ`h7J0$` zcG|02m9$Oi)vO^HI?jS-vav)apf%@4?lL~k?UJqCV%~P0aj*9Q7Sd+u*bxj#Og5I# zt>2WswieDQAu5!oXWl-mEHGVKX64z2p-_O05 z!6H=cakALo&i#+=r9UCIT!qLnf>{TuS(|ms6-xBT$o22qY=oq5?%oWJHr*_^^z}56 zLX~+UlGEgq^nUmR`b0fJ^$##|R-Urig5!x)e*F*nV<={d66(p@G@RY z6TPP?p5ckoOBhdTwT#Y@T1j;{3kmw^`4kVY?+-q`?m1TAS60rWM6i5sPcx#ahqz?t zD9Ux}LW&_#e=j`>Go8qO>$pdFi4bT`b##h6*r#%QdVY6DM7t;XcKKzL{-CeBDz#7B z_9GNS8h^Ch7lw*1y|EIBw6bHXxv!pqx`^WFn0S+Zi*`>gjsgRqWzY_rLtvxXWfX&_ z9JXwV&b%Rz4_7yJJe;W;YHBct4g8Q3AM{gE*vTb$fWf=8xm>`ph*`5KH8C;2PP!oR zPkVb?oDfu$yy~b;r7ir)M93#x(+19O?jd>zVrWc1vz;7x>ZiKP=M#usO~)q*woG#e z2gS|akLxp0PSob4rdyFBlh08);GL+SKK86Gr)T-v^j}ycx{3>nXea zb-8(eG+=PndR_6@9yY!ePhJfa@GnsI$&T0l`0+X5;_jR$IkbA{DUwKd_b{zA@KmEHwJ}|-ur_v^a@paM}X4S z;mj@a;^C)`QK5RFhu?)Dem40{#yi%&G+JCG!rKQW+m?Ge=SeS+O0F?t_KO zv)?u7`X-!QWF}YM@{P8;<)WDlDxIrV*N;a$bIJl`Czwsv(G_{yMxWo*0ZY_nP~t*V z3>4KQ1Wm#x-6CA4CR`&gZa?v3_?}hqqkekx3hU3ghMS0RS5Gq-=xo4$y!xrddqq)V zxfjAF^3%Jj)%zrRiSpT_8yZE14?b{SrB3qPh`TqxEXlcR&AXRM^Qi`yO$Z~2VF8?a zXemzZ=nrR0RsB0$Ulk*~R%2E?zoXu@B`(s4n#}c`ZAU`I0$yPeZJqGK5VfGKDzE`u zd`!ngsAI+*j!rzMJ@LG=sc#SSn%U+|R>+Cx!>LvlX1Ys@%p)ie9vx6XfzXZ`MC*u(zAP@y>6sPM zjBjDN8WB%f{B9+9fcON}&6D;Sm6P|7$oBtuW!#xd$W3Nq-I~v8<>DWgG~4lGV0uN3!RJS*;{Uk53CWBPtOld3JFdajRcZ1rs_v6iS(xf^9-ohA`#3nPVox~_Jp_xIAs@zy94vf=lW{-GY zp3AFX_DD_MDgAad_94!>i8Qaixsc5D%q3Qig|OUCjfC~^egZQFI zW_|@_w6V;gk@i(YUoUOR4!OD}+?yQE89SLsXcHHx*X}NN?McpFtQKlFLsv}c>B-Zq zuq)e+XcwG*hGP332a3WMJqJ(Rpm~T}1}B}-zWgMik!kJl6nnC^b^=Sqi*7+>ZwpfQ zt3_oPfyDZWnt#ay>~weupf2shZNFm`|Fm8sW$j{993~s5Qt0)Yt3CY*$_bZNbpcKxj&q ze(lWD($k?k%`75gqUkp?l(o*+Im>L3bZ(&0wBya7c~}=-1@uJKqOp7dMRm5!dYNe7 zdi%-Xc;diVy*FE(9O!Q(E*JXxlo;iWdjRIc{~fx5|5xa0UNIv&D6gOp#S{1dn_`Lk z8=orfKb21SFJEOW(IoV_qM^J2_CS0_GcK-_rKwx!u!QsM(a#EWQh6O4<-!V`tPyj? zG@{cw-Ja$v9Od{!J~aa^HugL>QtC9n4AEyXDA8=S(mzgXNam1hN?tKo0>##Vx+Ws= z&#Dr@tpT4m|KZDjI`(g8|8v5}{Mh%8E2WSDxJGA}x7ngNQ!S+8hap`yIyh!J&b=$al>U}AmWuLW2QufLd!L-)Ji&o31^&XF$*)r}Kva;?>m!^vOj&36@tZ;(y?z(=a{;DL?f| zZTF#b7e!3PB>~EV;p&$+BzO*whsjYo+=9EtH-GZVl83nW+u29 zM9HsmvMq$6si`L(r-^b=J-IMt`oxV8f`K)hg2!B?1KM5^z6P#N@u?*JkLxXuh=7nl zzEx#aQ|e$-GEK^AB0K?RS39?7v@YExe&KLYWEbr+{1j0l98?UO-8m&9QSs##VI5{CaUa(ui7Y3V;|33mb=?b4vOG1P+mQq~Uv(m5LlvspzSxm?n=Y4$2)! zCmQnA6VG}FNy*09X)!B(=Bqi5q3~Q%zxwZK1skh2@Zk*q-g;j1h0^t}+oV;c_C9@1 zVg*`qE%ZB45qht8>Gj#H$_Z{C0SUJBk0%PzpIPOVf_L5f>K`I%hf3+JSX{CcuSZs*31LO|u!T*<3Ahx7xW$ ziJX^wyosu8#Ow?M+4<=wYbvx*XW+`~KTYt3Pagx1qCS3ku~~W=-3Y+V**EQcrH3rq zIC@GtiJ)NC#`J1h*>Fy3=f-)fsUkaDEBo?-M6m$kQ(BTN5}e}l0%zS-o*Pp)H~Fmg zl1NT5vF+SNeFq0$G&aJJessGJ1#Vt;&Ux?bOyphe4W}bj41knt6jY6twn9T%t?@U% zOW@^As3NTQpB3a2Hyp*Fu}|^fb_2eaf`eFKsenGdJM3 zrQX8(c3^x76;JXIuT0z`*rcTxAW|hHn3@z`?hyCzD=70opo&c~V>5}ix|iAi$`aiz zKow1+VXGo$9Kz#M+SYY>&FMUu-j;j=Slqe}brVa7v6+NalAzX;@>rPra8nv8x^`&b|VTLmD*-3Ti5V zbV@89f(p!lR~we(CP5Cj@osVGdO%~_MzcTRYDBo{V0_6SI-OMC@7T+H8RM_wKi*Mx;p}aXUU&DWd2F*BJnm{t~=bH4oRbRQ~UKG)q*BtTvjLghQ z(5y)}ZW`=x|Grc&Y2}<&{S=*y(LyjDpv(ku_@2q)kXO^n#{!8Z+5O& zOVR!TYO%92{smeQ_LtsyL&-bs5v8VMHd&cJkjhn&HYgLJK|VyTW0nDu{*$en*=juN zV`nnpxSKhcd~_1XPd44v5we^ZZZusF^E=$@d0_$;JUmfb{JFN{aB$Ac8_%8CSU*6L z`nwm~0D#9V4wgB0{vSkKlPpC1h%uw1++c1|+?vztlv|~T5lS<30j=B2?q+p_2$%TN z_1fQD$W9p=Y8(D?Rh<6YYOiQJ3>_g?HnXb)JeD(UDH!kpy|J{i-oAK`K5^1N`^;+Q zGxDSanP$2pjCU*(sy-FgmsCJ=o3bsWr)3H&X48aTe@-aW<0mG0X_sY%qhO1se-P7; zy+cFl!Yl}wu%8C`jv_=}07p3)f$WwdY8vd9Q_L_0wGgwJ zWJr~2vbH;m-6)nQ-t1KmA52n%5AbRx@Kz=FUH+;e@0>uCV%8Ane2{<6U`%+i!te~` z)!xz!;LQ#_1vVL$QmM^tNwTjHa_`?pOV`xYc+JmYW(M4GwBML5HQ#^u;Sb-i;K~=fMVbT}&vXHlwaLbFn(*Fa zod3oUuZN#Z)kD+nu@Ve1e8jn1@QI3`{&5tE~p=TY`Nt=8Zx+J#>22ltv6zk#(I=A?lnygTNQeM z1R)=7Gw!-)%fR*Xt=GzEXfOC|=>|DiZ?5#+A z!fr;6(XrX#eii&wd3BhwmlzJz?sdSs1vwZUgFBZabe}IfHhk+C66`ts_jZ@y?;~MT zFR7T{S@)_%0r7Z1>CHRDE&j_%)V~iH9T?k8>1XP}Y2tge8&;8##=fLCh(-4RDCEyUiqatWnqpRu$7LVb0oJ4LfUPja zg(H*2-?Ur|4svSc^xiX4}=t0 zZ4VCW?J3WX2vGW*UZmJz){1}lUNF+;cr$r;*4m9I7=8+|-2G9nhZv4h^YStb3hlQT zZgn?D5YAB+8`|zBXx;ek&L5^U&mmY=hSKq)zNO$sBVpqdu}i!Ar5}jS+Y7UjTpYE4WqDJ0z2K` z0$rmcbUz|3eJbjxPyC8mO5-7t4e7X2&x7$2nCbVcfi@qmawmnk6Kd?X_}YuSLAdXk zhcDcB;rd+8l<4V+=j$2F7{o&gi?p;w|I7o+l|R~;+}vEoQb+OXWD4>tfq1yxjjX53 zU%*3|-N(`#^cQth218lg$1ac6*QkKOOQ;zS*VwgN3h?M#x6+T-#x^_!VgLYu_2~|x zN`1fic^nQlb}<0wECif~^)xpbuiBna>>H;xRUWrd->zSxNnVFRijpHK54_{BRSxx{ z#6GAFuCl25uA$G-{~d0_Tf=S-v8%3(0$AvQ@5!hF+G|axdtCgg>AocVbMeEj+Z5Q# z$}s@KBMsyKHIEW(6I&yy->yRZ&U6A9!-0XXI*Z7A$^V(EbdVnv{>Sd8S@Rq2&A_C8 z^d;NicEj#1XdLG+ePn7kvfz<<)jbBBzOaSZUkj{ddI3@)h1B2b-+V^OpD(Uz4o_!{>mvtEut(W>&@Tf)l95ChGRNKn9 z4GhES0ZK;`z&)c}W2K}%&-B@!pxvw&XQwh^wK&yikX}6#%n~`BcM%Ub-z?^J-^$Gr zKR>My$3Kr90ZM-SIPM**&wjz$+$!CVED9NT+t@kkL2i2YEC9re`bqKJ5SN;mB-&X+ z)eWt6Ev>1rHr}%T9Alw+WFWw%c@5p|DP?nEJg1n~_6)aj>q&PfrY5nqkF|5GoC6aT z{7mFd;IL0^=jI2?!tupwgEunv8V=z+dVB9sE*NVdbgH|g&eVE(MkfV=iQSYvEjjVz z@eBdBbw=2PSRPHdj9}?D$L|Q7C{VU=AGhH@9eynMFuQj1h z`$Y5ec|>JlyG;1LVNyjsZE`*OLZV(SrJG4EO9LY>^^JF>mps9U`drZBp(8hTmG-PH zCR_gCb?W87mU5_nFTZkz*cr}(s*u2myK{=`WLw3BYkyPR)tQ6eh2hpnCUH~Oir$|% z`0YMRfhiSwAiq9JezQl-IiIK{bf#PW8Pe5Pjw!wk-@31(?VTr~5l>~k-Cpj@55Ly; z-VWxtABElhhK;;T^gsNws!^pa0m}EG5N`>FK#;#rH2YALHf?@y^3cygj>6YPzsusj z5ppqI{IIu$B^2^sp?%{${xSw4z5>{ZWI7*q``zc9Fw`X58h(8|<%X> znhf__WWIhn69WO7bQ+cf30m|Q2IwZEy*k2?w(|( z?UiJ>WaT$u9E8~2gU30FE`;PR4K_`;tyo}3*RTc4zKlqQdprKqSh1{S}9 zBMuWQ!MNF>1-=K-R%%mwcuDbNzc90MI4BmiPJev?tZHQ3uHVv{OXWu_6l_oA+XFk; zfVu|je)u3KF}uG4?22~1EZpYT9Tx{%aONIWM?!Y=V0oUDwnS$UrYecM8l0wV`zU=_ zcRqjec8~5}@#Lum-Qix+qN9Y>Zs4B(E#1}&gIMp6vq^sur(w6wJ5y>gF1Y6%T=b88 zVWj9e0^H_(aj0sZmH*O+W!#-!ncQcCeAS((#QP6oX|cx-=%5MAt@>a}jy`9#Ms*d8 zX2a6$M1()zW%)tI7?{F>wS*)+HN7RfR-mLMb5o+ZT=hki5id%m(g#z3XKc%u;93qsDyp5%Kb)*_QQz$y?2q zJ_}lyq&@ig;%XY#W0R`ZDT>XIEW80KWyOM}q`J8pgX!v}vEp$nG)i%Dz^=LHOm)o< zTTxI-GabhM4WBZ9`yYH-Quw-1Im_PCvN9Z6)zzbA;KzIH%jfy?gt=Cg;W6Pulu|9E zwnA+p=lj7aqs>qL6&8?^+WrcVG<%7JhtWu?XVn|nuP`YMbcnU3aWV1*Ux=vzcpVks zT625n|4Jxf@7n(*f|3_RYb?|LzZfo;NKjv+^b@P2u?lQTokZt?`2!K;% z-cf~ZKE9(41+4A0&bRi>QfaNj1|20Wo}{$%x>O1(IH!P2P zo1pNrovrX-qu2W2f~j)Wya9E@X~!HF(dS$hp96nkqO)+KJ9LDuPk}s-CR-5Q(^b+xH-)Pq z=x}9kxDC^Q!6QW2&w7-q4|V#UTg>8TZKpFs-Mwwmvq@drZ>!LvFzfSev5<161axmZ zccY6Rck|2MB)NtkpnAL>+p@8zG%jtaE37FfaOparv}~Qx7xnHvz3>NnHgmhAPy2DZf&TGSDGRJB9sa+i6`6$(+f=wQ z`8NRN;`%hxv_{Uh<}PNKq!~3zPoUoN5CRDT^b7w019=A*mMxA!#B`XQ2$o;e){Lg! z#sVFxr8AAm!YrW}*0XR|Rp}jLUgDC@T({49*Y^FA3Eq3?wfeP!X(0`>KNbA^Z(j&r z5hu>*u>lpbY-X2X(cb8VEN`^-q89>_3XWDMe*@28XHPlNu;-Qjt=jfQik~cw4_Qw2 zlmuJrtpo@JsAFuw81cF26dgfdnLnZ@(PFw&SnZ|!9!021`SE6m0va1YtMiiIe$lI| z3y}mU!wJOB9`L>`=a`~-_gr3pE|~=Ak}gsu5~y&HNtR~h@f;Td61QMnl~aIMFj##R znV~T;`Y+uaWIFzpE5^AuYOgjiKqTy;vL0RAZ0%$A_V&^56?o={^a(23U-awJyiStAbz zt=H%d>Q1oV_n{EIS5YPpi%M$X*oUIG3FO8pOLhGx{F1k8E1rdJ^SwoYQ zwo{-p8u&{l)n_YU?#WFY3;{P&sPsA2LG*GpJJsD4`M~Zy zXR692wlpiB2i%kT!pc}eOPIzp`tT@VV${Aly-J-A#u8iyo8p4{BAdr z^4K}_gaWE#lXVCrfl^>%46%F4cd{$vo7zw#-WYRXd?i3Se#U}92Hge}d{GMJ9ZQV} zv&)OhmztTA#(i9AXxu7O)_ScivHwG6Y`zh!nJr$RLQE)MJybTx|4W(_5vE1#&NNyc za@j5E+OATZc3R*+^%n9Q+PKnPSg6QX z6fgZso^eBPc-l{^y0<^8pRTlfFc)l1*!oAcWE7#`_podoRo;W5hS`*lu4-2WGZJYJ zAs7xQmDetK&mM_TU~b;5fz?be2njdU1HjLjpB-7cyAvc-V*mNh=MZ25NU2cNMCaRZ zq|~5HJy#_1zm;s(_0AGBh&}sH4i@C5ZNB{oNsf0uWlN$ zUrx3Bi~IQa5=a!LG5@M2Q|T}FVj9GexjafDAowHy|C<*hB+mXwQHjO@$Xr^JMyQ>~AO8B3$4;+I?hwM0W0wz-j?P zl@03I0@*-M#-c{*ZGytJ*q^VwhlU>I9;;{s=rPg&rAPo3(;Fl7X;iaNoiVJ&h%QEp z0AIE4=exZ31TY~wSwjKb>Cf;j=hsc&x|BjCO!bq(t&w$Cld&`=q&V6!;Kl8DXSujz z72cwvH1aX?r$0>ZB-VfmxSS&pgq6)PCl)x{aB^KnKK13-co6qYj+1m$rgtvAlB_;R zU91sHY2ND9>vs@_i8AI}r}Mszzb&rPXe@p*Jj9FHi0K!BW@8QCFIN|CzG zsk-kc0!y~dgKH;4UZ;?*{#QoiJ+b<6_*BJJY3TYOaT9LJ;{igBZx5+m$G;LVxZv&gP z7!{S+Kzb`pej?FkKO{_mnba{Tj=VmzOJ3E3v3VY=h}d+9ix}nlTe^GE1uYt=SzDrd zNYBHv8XhZQXwYjvBnT|sAnRj|h<5LUH8MHl(2O;RK|ip>cen5)Gy>D-c_{rBKgc|V ze)@YSJ+IO@8a84>`zw;z(0&^Ro1t^o-j(X>vgs5a#G*kzcpY)v2;m}B$P=*AL@aTT z10raEadWhi8tqac4xBM&9-PMyyb*;b#a?}=4lM>vB?0mxS`jJTwhNAt(8Y1Ys>C`S zC#TIg2i+UtO?vW_%AmT)edeajb$le{o%)hFOu4*4bIi0!g_rmp?Ph$D|8U5P@;Pn! zef&lEia1>Cy`YvLcHw1Ff5=mOwklp7taKdkQj{v-FFx8N)1>Id=S?gl5+AQrM4-|B zFjHS0g*0{4q6#`#ab5S}Yq_KjzJ^K_QkpjR&j=$Kjc~ z3|hK@R$Mtp1KBb`Sh|d1lJm0DC`)(*-E|EUHUS&?VvuARTT<10#p55T;&}~bPBjwqmgA|& z($+D+p5)kFF5I`dKzYvoyg5%~UHS&;LRl$8hN<8|ZX=Qtm*JTAI)^+dUh0ZxRAb5X z3!jTDMRKfD=?LMud7LyKKIh7^+B=7Gh)`-&NX5$^V^_$}Z6qGM)B~+I)Z6F_*GIFb!t>HH~EA1-aDX706X<(w7m%fU_~A zfe3c`)}g8TE0~+WM{}!K1#nMv`d7&;q(I zCe~5)hxNS^KV|2!-mDHVlIUSbas=l{+I#tAKeUSjZqplMm20}4oED#)@-kv-8Byg3 z81z(aKg0}Wi zs3Wx&`zW}#TxvqwcHLcc?r_>vA$h*R*Or(oDb*$Q17z?~E}ebjBP*n*5#-XN=ZtKW z<@XgfbBv&4oqM@dZ$92onG=XN#Pq^m)^W|{Rpd9zuz8{4LfIrnTPHbormWlgw~`sp z*}1@`ZSPD!cgzqJ;;B!Rs7$s=3lqO86P*kBnIe;1-07qqaqln2MXoMZHM`|xgogp# z&p9s}o$FLeR!wNI=Z~FSy)L~loLEJB@<%9r*t<3gLD?WM!)9*3F6~}f9F+zd8|WyR z-lDS~2pla6ixQ@v!8qhOSkIVL6uG5)6h!AhLD_sk@BvS#0-vLlx|>L4Q$6c9* z%V);DUtPmwj75SeMh)>Rs5t*P~A%oqN?}=;+X!+gi5YPtVR+QZV&X$F1C>T zz{n^&XM%n!#3)|G$fI0Jee@A&m&LlA!@j^jp~1b6qSTHA=rWyY9^`M0@}bvN`6xx_ z2k2_ljSiq4j0F9$c>=h7_H}^BZ^q`EHo0)m(HQ%q#M-4B0oBTdf^ql+kNB{pkx*o+ zAw9bWtQ?0LwvC=LYuX0sXCV9$CZ+so^z?a56ROS{DCx_ns|!Oa3H=aE622s}q2%(^ zqxAOZT&uIjC?DGJ{v9PhQfY{_^U0%l5U#?uAPmE0Z9kl(z z%qb15)XK)ii%mjlqO?J=*TpKxq?}5t1faSb_ibzK$v@Y~XU$v_DdNl5aNg>Kp?|4U zEQ_5=9EbxUo*27e`jF9Hl>0&Ksd{NumA({bH!=oh=?+z5q_i+Tc%KE=?LuT(&iD$kPH z!1wiVXs}GiG~H8tJ1S^FS`$4swVF?DTuLlp9O*B=$E{8SVu_(%pO|6F!F!&0;>Ox# zgdm56Rj@-LYn(4bo+bD9-}$~#yHk`Rmmx#t)Ue%ZwBqkYXAUmQTZX1$Q1it@W?-U_ zz92_62?II}kxPylGeu$7`EU@M7MS@{3F(=XR*k%)@tiK?ZFU(LoI}F1m`i*hd3&ou zbzXnEoWZS(1q_wpB^d(5M&Gxbz!h$GDRs6S>)4L;oFP+@5@yggF(=d+PLS0^UniaR zl4@)$mmDqm>mhRch#ff$^0vX(-@JJ-m+Ea`;oYf$MM8p5K1++>X; zAPVn!!~m4rDffP~*7FV6jw%+in1((qi63#yT3BA?_cKZ{iZ7KKXfU5`q^Y|~?Cuwu zn&(OGDq~|Vb{I*lbh;mM^Yj{OqheX}YmGFHgX1wPf*u-4_8B+;VD1>{dchg7@;HI< zn>yC%!FX9r< z%y4MBODcxcn5c}UDKQ$5hW7NGylrMWI*S_G=!Aw@41Rx34-;X3-`wuHF*=^>VjIs?3OgktGXOtSK5)s? z3mR~~a92_ptMx8$w4$UNe1sv1J@GPlDr7Wm#RH?eoNO>hEBAjccB}zD|Ys$tO3_>qYTxK0^j! zQa0%VkgS{(ClK;exJLPO=WHjr#L-hSADO0djgs&)Z8AJa8OY+(wNBp|`bupQSl!S^ z^l8E73_ z<1kM3RfxTgqx?Vyx9;S{O~)-WLjYp)wg;u6P6Zd9Yi%V$=>aTp~+wFEpRIr$RN}6tB7!~l(W~^jJRK%e?SZPA=V;i z9j`>+H|0q-wTQQn-2Jf@@LC}*nb&;(Dl7!+vYEw*n)w*-w_Q2lh&yYNn}T}MvPY`` z|6cjlg;u2?bKO;C%>Ke%Q*@xj`ht(4iA00OTiS8ki0G``nL^$3{pcfjdaUE>hKRCH z5oSMC4y6A}D2clIZHz?7n_>^IX0z{1FW_$li*v1C&{qA}Q5vj^dltiQ@6<%Md^`K| zsIdHP6f%Q!HciFm3ln|bi!;pibzl;B#C5>vt>L+HSrFU<%PvW&qzxMGx4?vd7o5Bl z{8k9Gft6pTB)gioBt@)h(zE3xXE zVw>Li+@YZ@Bo^kAi=?$F(YI!(VNy4P#Oh!y)qfq(4R+{3Fx3#XksHwR%CUncyV9qN zt$6h+S{N&Tcv`qqyK%1oDH(OYEr=>dvHEvn*SlcGO*d3fYfNlVsYC~JUFPVsK_ z;I#+@AR7!HZpc&zRZ0c}Ij{URV+skbqWHR)uW;2tH*l!+SGsv;XwEE|S}J^$NX0OX z%y|1JrTlgq36L&L6hDrARNqI{M8`SmpF)UABe_bc`@A^4neOen?&=!e;|h74$9zaq z420K8qC%JhrLC-Hd+hz-Ci{1SBM$|(qFwY>K{-{mkKKM}WSwpflD9ML+>jA{LKi&m z44+>8vj>51QitZ#Eb{SyF{JTk%tbNg2x)kARn@cJr{O87zd}nht$Kr#u~m3H90WZC zA}a?Q2qzGi_*(nZDx8g-`UTSm?a6ncXWw-hbDUMa4JlnZGm<=$HhY__4_Om(Lsu4d zLyu{cChe?425{HN+DCFy;V;V+yN+jUT5NMaIVd)!!`D*P~A?0M;>$)H>2R>6WRT@tBaZOkc zzVHCM>@QGzJ4xbWqd19+F7v{iLm(Z}04N9*&qbgo5MuT>^*}>jNq^un+il}dd`Un8 zlj@srOcc{qZ30u)5VzS~(|!X6%Vos{dosDB*Zni7nUM+YU3RI5h0Argf~h~PS;5az zCow{mO=@NpRyBg&9Cl+W3>@CBIivlxUK5QU%1HGjY(ZVk4kWEYNJmyoA ztu5}a4?vnTOBxx#I$lYe55|MKPJaIovxAW!w3DddMIcUe3PqmxR)?$M zy)cU*4ceQ*B$5k>E#?%gljTie5_HM|z@^-~Y}N+QTY@;n1ON&ilz$D+I7|$&hRCen zbT(Fs=e_r}bYIEE-1u>27jv1>1aBa~zCsQ|G>uDWLU@<<0spvK48aKX9F(|?T(h=B zMRGMI@&}(B7j-ZrUca%mz%adBf!E7IBbgSjB-Xpi!-2OeU6SyVXTqc^a>6>olSs+hZasY7_F3& zIhi-nLdAm71tqBn#zPt)X z8tgzCY7`m^XC-eh<8DJ+UC0j)0bwhqJ$rQOK}L;BgvQK_Z5P{Z45J~M#2Fzv3eEu% zBt^ZM%e1DTG%3$GHyz-PZz2bCJ#FaZ^Kc>>piWA8PO7|_JbCCdonEO1{nW6;nadcR1}aNkUB#uDI8NvK11Jh*|Tqd2dfGQ6gScY)Fp zLFqD+4I~FIrUi;OR2CBbjfR=9RHze!uT9D6h;so1O7LjhyLjXYmvr9*CqH~GZTyd2 zbE5n)c^2RI(P&L#CyF#PVeI4by$RkR?NCx=<`GS!g)K$68Z!w98HC6f=KfR2AGg{pf4acw0X40k1 z0|(y7JZB13!{do?p-#ZH&>dHm{sPX`UNPDN zlgC;vn)9h}4J;#&ps5o!E-AZ;8%E^RO2tJ%S#dkT%LJ48poFZNeN^xfM_u;{%Q?GMsg>0CogCcd@`C=b06PYJBx{@-lEm{P z>Bu(%fzxBB=?Ztk6&2usO?L^4+zs;rUd=PQX+d|5Pem28Kj{BdcI8n?ZtcEvx}8eD zqh^_U8p0n0n>#lEo>)!uZ3-*5Ze%`&=zvp>=zh}R=e%39A96PV!{B;_H7+aut zT|5bT^Ja~e*H_f=s5c^YkLr!arZlY)5mk&z#n-QiN71@-rtbqkKeabFY52dbbNbtMAN&Z zqe!!f1P$FYX&G^Gp~R^b|KnuDL_+^I&lcbu|5kV$l%W)t_9Wzj zNELtyz&~cF7Bg7z7!hfo=(bQrjTw90WB7gTZfYCI^5-)_t?!?|eP3ofdsVo!+j{H0 z1LKtQ2M&At&FxR>S3*nUih0V6Zc$#fQc<*NLJGJnm0GW^d*Q?L=9xpR+pzPn0s63o zh)F$d6@~!}+QBoZ6$# zS$YN7uCL=@!fdwXJnyfHL7m%fW=}J~=|+$~%$+@?(aOA%eD^qzIt%~?!v_kb9)`2zVXw+<;>xk`?%t@zpSPoDsUhlc|r1ic+G zj0W?Ms&vUxZFiLAK5tb_sXAXo{T^34QKWw?6+8skK~J(ZBjJjdIQF0x@t%iDo8z1T z;31@ZRSxPMwpdG00BhGL_~pl$nCn94H|zI=?c2er-??{jTLuuxYWCkwYD+q3a!i$Y z_E4(L4hst-NCj0FMH{xbqXI;6fGOV1*v5kFe`$=~1u2ua*H>{*6F>d*2&WI&PvJ=B za5mtOe^ylmaz^{)NxU<=_J`a{B;TAbFNj&o_#Mp;XyS5HPU=Fk8^>6M5Uk#jc<3`@ z5o+sZ?hq6NFZU?Xi%>nd@oA|u5oBdR-U)^yny3!iBD-qez6iUKz zvgMVGr~ACZ0XIIglveEau>ZM6`rJSO2(O}}xVO2_Bn^DtvWh4@w*)y!G_V{-?8o#N zhL!EMAV`48daJr+^n&_?ciq5xiV0`%B>>u8m4{t+yOu-wZxAjUfY0lqLS-{p%-e@r zLLNe2QYdG7^fVVXoDIY~JM z>VJM1MM3qKNhGW_Iej9VV$rzKROyV3;M}sHAr%wzm>WiT_)J z0HoO0y&{0C%Y+4R30b?~+LbF;qKl!_)@Ws?)t@vei$;OHyiQ@kp$$})LdJ2RYlZPe zs%&NS14;J!NxmZaJHno(c@&(_p;oxNrkGJ(L-@!wYp zf>MQCKwjT>K*K;cdIpmx1lkoKo}dK)^E7S&N1nsDCaIg1jC5WyefQdiE>{%LGp65h z6~(y(N-;F7V1IqXAIiX71hTycEo8(_`)!wNQN07|V}2czaw>tPcXj~R`INQJDkWWA z8_qHc6A859V7NB8kmB=s2Ysu9VAq_sIBMbPTmP>gZ`Vb1z%6E3OiU=bRS{Lylrl{S zE-4UIItr#brd;{njW12bfPTS(QBYMtG45HTty{$Ia$kZ<^HDyMl+@1r!@3&KsT`}} z!BG=*cWIJEO@tQULS269iXZnWTbZ0JN68?vf_f>;6{;w@x`cFeJS^(3VAGD7Px6Go zm-n-?7gzW{8-ff&DwWflrk~J3prtd7T^t#ntAK%a8isY{N! zHO)Tgz--eb_y+$E%tBtyEqG0gvJ4yzEv8^)X{S4eq<1lGg2N(#6Q(tIC3Vsl!;F6nDhPK&lBsR?DY(E;Lb=Ilc*fJFe2BFwD}zBtslg!P&)3qCb8Fa z41;;BNe2lfqe`sP@#Yanyok9>9_;h&k$BiQCj){NF!%ymJEdF2!)IW)Og$g7)5)z^ zuCz#j&m#GJ@lKtzTIQE{3qg-6o_Vb`{fVSVp|>~irv6cvlAff}pa;;(=={nZEA>H> zt1T6cx&ElPIBN;_1rg=uE^(kc2N_AwZ0)glHvYuN7F-s_+g8+q$NTlJd`f1+7to1= zNQ_p5bzEwrN0)U!`;0NPBOIQVc9tkWGYj3 zp?a0-)CHMQ$Zg{G*C(@Ka~nfEXoszm^-J%oZ{I#^c4`qL{z4{vnG3>~UOPw|^RQTSWp=i` zi+)Zorpb|F4CB}X5T+dy{(OvS*>D)IX?Sl(git6t0u9{U-$B8&D4i0-D%ne{qjGE^i zmg096V;yhCP06H85t(ggBN}qMhZU?kXSF*%yoHN7Mz18*+euO4Kyj{t2Y7|=VC1{G zafKjG!o=Q)s*nCTq^1_5S5B~uU)z&qFTl$798eFXQ#+XT0=h7QL?VWeq4b&gb|s-f z@TuMa8B{8$c9Oxl&c$}gjyFHA7CxNrb;ok(9;NB`vmGTu2gGzGPXOanP&qsg7@s#R zl#uGY9EG*;RYzHn_Ueb8;`GVW#6|SnK$)Q~#rtja{6I(C8I!y&(FM_Qs(NeHP1ZsJ zlzqYMmkNHV0>9>^(r zi<7WcN5X``M4HMDaC!meS=(G}9xq6yvy7HzW@q!$*%J~(@Pd&V{5h6qAm&RpGb(BA zvKT4vBZB623IzI+CG#Pa)kVliGHI994J5n}D=jdw3Jc<{dT$6(OVb%kl)42s2D7E; zAp6OYS{)xit0WzC`RFPu(+)>nZd|>^1LJ~BZqWQap$sq@!vl0#dGsi21aK$_!7P>l zRh0E+Y@eEK{5z)G2~bb6gDCr2R8%oI3$Z#>5Xg{y=b@8&V3UF_xMbR499kQ3fNW}# zcNiH*z(ZIp@g$m->LApU;NdV<%>9Pxi(KYSNYp{Xw4|uH!q5f&Db4@GDL+C0 zgm*0I!}W0JFY87-V_>`r4=2fzn2p75SCqZ*NT0_WAXaCsuw^3RSGc$bOQXKp&iJOM zTiUXvQ9X8sC@LOnqpB%okw@cf)*JviP${0ALv-q{<@;y8MrtuqpkSPo1 VP!p%L^wuIUr(E1Y$m5s){BK6w1_%TQ?(XgmArRbM0}SpEg1d#_Zkgc09fG?C@9)UDcaP9LoUlux*s4ZV_UDwK%L&1%(_TtYSdWXjW+Q% zPGq7?`j0;eKFFy3ATWv?HHq9|C+nE(dO4L(2&4x4MTqRWV`JHQbo3f>-8>RINCJ{j z0Y@=1xuFj^I12N9dZe`25vgusv*tRxWxNTaLJC+6C#s^`?Ji!6rFq;U(|$w>$U#^% zg2Bw*6B1(8_PTDt=skER6s`mWkh`<~<}0a_o3@G3xx8qhI7dLRD0Fn&_yMoga2f0! z)w=_TU`~RoN7pb`dg!Gv;l@9Q(g889#hfYq!hj4ae97yiSk zT8x-^Gl!r1Ami-mSm%<)f{@j~4*`hoFr`!Yqk_ftJk~2Za|6lFIQ7DoWTuzINzJx? ztkX;7ez8oXN(=CWWHuT~A#amt^rW(GZz!m(+RsKv#uo!PX7Ra_-T;|LAzU%uDIFr@u zvYzq>eRZ1T=SLWz?%8{v4`0h6|7y>N0psVf*HTN$wek8Ow+&w?9OL|DwIgg_Ov^UI z9L>lWY79c@a$lN*_8I;0H^0aGZ*$5QfwuQT+Vy`o@fR5t#z7uy(Ce8MXB()r{)Jb5 zRkqJ}-f40%*(Rvhdvk~@vklKL13G&P4sV8*-owGL4!zo%F;Lz^EXDKZeu}m&OZviuXWT8&t2kBDQm1B)SFX(+{cPNN`k{)Mb^2`VkR;)*>xPppGgWK$R+HanAA+Fet{QJ}~e!gq$k~|`8V%sAnbYD(4-XaBF zq2%yf?O99M@V$lu?`t(evgxwcjLMI=nT$B1m+wq@%j;?51Apf<)LgFp#N5E2a9%5^ zqDr&{ZdlPrM~;-5TMxtKdM7^mFq*gQuK(?Sh&2+Z)&0O*=9T|Ug;(?8shy8!s`O`) zXq)1nr{nTxBB%G?5BILAKg6?SGZc9PAZ~J%gMt9+=NNOui$*t_% z$R+F%GDe5)o0!bfSZC*@! zVFD|Rx0lhO(4CvV#Su%XKYkr&^x3CGSgk8YzWdbP=Ccr4owB>gnh}|~ll5vOCuCV_%p2y*xX{>KVhrNAE2s?1(E1^ zBLJUzI-5Qka?_X5w+oo`yb-z{caQFU272${D{Q^W&jcUqIG$iZtx}SoVW>k-6+3wE5nY#Aq9{ZfuIeN71VtXJ&dzX)l)JTeC)+t+S~$4%37Q&R$LCq~|(j%A`3 z&x@{WdM~SgRq1fp34nwCNO-5+*?qewO4ivG)%AY5$MF$z$IQ!j^?|`uo^jMtg$}JQ z1|byfn*vw7OTg61%w>MWo1cmp7k?2d#)BklDWL+s3TcZaSTp?VG703q)0NtV-uWe{ zJ0ta1gnd;x!hwh6`&m-%r9D?kUY&p~i{2lqZnGnfjcKUKxK>G)a`~BRe$!ylbPn-O%234DEHTT~?$V>>-O$rme`b_YSEa(U3jj_My zlz`zN8MZQ!0b8ePg_46?r~3QKuWc-7$`Om{-d{?8E0p7aZbyP%lR9cb1NYD}a*i_g zbs$upDp4y7VRo~uVS^!7!-?&JwyHEEUma6cy3#Re&*7wGt@QB7lIA+xcI!Hg8ku`W z#bhaTG12$Z6amg6+nqp~865b>o#oOfFR6sfqxekaev|B_K|zIKeIveG!xv)>7(wQc zpTp@JIXCB$bx()XtOiW67!8vVB_#q$k`bWqaW>}^Xh|aJlCZ`sGa1r>red%1R5Tg~ z0tx`~q!5pH*6QS(a$5P8=da=-K)??#qQ;u-tZvQP;rgkmEQ&W?E7Pn)gWN&lQlY~2 zvYqbNCm-BYrszyLgUrj;w|H<3Ur&NXdO^GqSiq97pKJ-_`!AW-;7Z)RWNHSm2F>0Mk@r-wTHa?22PZ;l`kIt1gMGVlQp#Fn(Bf*4Qn#hg@PixHMP0sE)rkw`=yU5VY!+}2$~v(tpztelGa zlHzO~ZzI;~*g}R7y?v_5#$f*~B6m0U*X##Y#rHaU(#dIcJ82Q3J`+Q&(>lpXXS8Fs z2&b4B^F?y`pPLiB|6I{^i1Ix~p4stq(qIFhm|)1!lAxyRS(YR#FVLALw)AU9afP&jRpV|UBBQC5`hntm^W9s(PBD;cKm*L z-we(8WjR|jns?hh0c0TPc(F<4xBEplPF5SgXj@d3jl|g*Z0K1MW1tw~$kAd(=#=d; z&17A-xZLoHeX$We&9~h@1yfbd-GOkH6Bg}euqDNPBfsj@narm%CL%%v;2EQn`G&ojM~eyFgoQAD18ERfN9A~(zf@H)-s;Qw zy=RV&Ii)9&XDf5ORb{9g?;k+$JvZ1rk+bpORCXK~^ZpJZdNdg`#hU9+%}`Odg_||FC-4 z3DU~q8@*-0pgYY*S^Tc}`RM9l6SvGxy=FotIvZbWk5r`QRD5aVq;`jRZcXxZ!M>JF zIc#nb$xkqga9Hmtr$(~hWfuXNGARULtF+T5Z2;`23tW+$rOeI=E$48FzV-|frpTaf zJXH#IN;0wNZF)6yc{t9E#Zn}BB^(P!W#_ ze;ZcV$%)n5eJ)~iYY;X#{Tqg==iu3%nQMx7t50^+P>73)z{Nrf#v;5cDTQS;HHy@Vp03cJ(seR7Q_~aMu1A zWFm7WtqEfTAJ;2I(`8~_P*6=)bpB+}{?+aQZyE(h+=^epXP4Rq4tG2)qa{bAZmL{!xve~QYr@Xt}=vgGmW@xb~kK?Hor!?=aw? zt`kvn)$hB`o}#&RUg-6x02efP_*%_@?vS#2KaqIs%1aamPL<6w3P7j1h(i`eqA*$P zDpt&DBYd}IJ+$QOw)0URtzmXXE_wOxCA?rnOX2m-mowZ^A|0rZThX_zJi_j8uu8o`(hf$zg8ULY8B8glnHft84VEu1dy z8|KM=)z93Ge}wja4)2(OMbCIp`D9LZu;ZJY4?B+{g1I?Dg!phBdc+}jd{mg*l$_H7 zo%ZNG+F!CUYu>}~9emJ-gG~wH zz4+C8=I4m70)Y9-S|MHp zO&n5B=xvVSaeSSE8|TzW!E;=qdg_f)fGsa9*_c=y!0PEo{GV zdDq0uUwyUJc)lXf_6IP|d%LFB%7b+b>%hKsWUh*5I1ovoIF8l<(SLM80&>w*s+0E? z&9nWNl8cZ4ixEa%7!Z>z-n7k!e&;5#N~A^{Q(xUF^;9@8X8aec zgx&>FvgALmxYU}hBe*%O7T2wm*~uH32Kh5BB%|z zNisPH>Y?h}C6#>4GlP;$KuO8xa^RyTHfi|KtAxZ%H35FTSAc`W1af)i4m$-(7ce%p zyuHVn=oZ)0Lc-I9AF9o9gVtv8F}F=283LnUO!s81lJ7YM07di-$&e4rA=wOK*z)&) zvClsWpUTP@*ep1&H=(>V*NJv+fC~8|e4ybp^g_ru2vC4+5}~GuWkf=SxzX#8Dq&km ze&*Py?lNCqJdI6#T2FrZDqjl=A{Pc`mG3$+r}A(e9U63O_d2dRM*D|(<=0J^(?_Nj z`OFbE{4n9geF*az5b)kVhB<@OE~{)-ky4ixD_S8l-`!i?6}xdrt(Xo zf#SC;Bz%nS8#l3o_Y;d<=oD2Z6Dz&|fN8T-6@ht|D?GYv-SX@X*(ug~LJk6`Kw<(^XP;mcopa}X`)8$+0oDup7jhPzADPy zVuS7aqqAvD0&D~ZB2Vb{TQBMAHl=1`;#>5a*ZU{ZKdwD!_dPf>3$YEBSy*vW_-q=s zc5O@D{h@eSprM(7enq!k|97QR${Z%|eyN1NDwVw!d*%_vas|K8N&*K0yf?<1O(*lF zsYSY0+PiDv#%=_*!hviK1~%vLi5N?hHtFd-y(sBM_$I$^@dv?hFK-k+-X{fZv0g zpJJmayptl-zQ2)3v_Addtu*&-8Xq?s#nUNxvf=bH)Zs4<&E5H*#;hiE%+rGj=H@%i ztLZw}mFZ#BluKu&<0M=Wc8c~G5YmaLqxz9H`637yeN#5qhHBw9oHEvYGj~2fx?@-Y zNkKIH4oBPUW2~?o_^=8n;i>vPpaJTf(tB1+vS7iBo&be!q9v67U zuMnpFcpa+5&xe9%qRNp9IQ$(ZTzGz3!0~JLz%;YD=6RX>|7 z_~ZeztFQ8rI4nqEc)4ui5ODrB{XW^r*9J}nB z@4!86-(u%7{%Nvb47JywMLF19v>1niyv@#oJ&xTDP)&q$i3Ik$jLKc7&P;>}0Gp|l!p+#;W-^p6mv*dimqgMVT^r1hf^+t z;RY{iZFObFfoP{wt2-1~emNQbmDVtUkFds;1^Fa0k@<|v%LYj=!z!EOOj`?lF7ejT zy17&8_#?r?_2ev)VZ&W_+E2X?PgOesE^sb32`)x+#kJ<_qKF>!mjz2?S{7aizHHB} zSxko*8_iiOzpihuOOdM52lm6pMZ|8lnTsb3kD7PL5L?-~VQk$Lp-VA62et#?f-J`CpP`X z_FQLlEVDAFQdnU<7^ zP3*pLj)il9J4$PtOg^2<-G!Tz-!^gx&)_Po-+%QtLm-onJri!y)pi+0R64UH-Xd+U zeZjEI`N~DGac4p{x&U#Mfm$Xjg-J>NjK76Sub-b9O@i!5`re^8i$<)6JKeo#^03D9 zy!%Jo``G5lY}-hg?}L!Saqf9^1=_l-rw@mnq?Tk!bbNeF`;r=udu6SKJ(Ajl6FR?j zW86Rsfc91wX)LaMGhJ-I zU2B1k?XC{RFnzxcf9*?swyZF0mD9qgTU=d$PX6le_Tu;F;AeM#FM#Oz_Qr}>;%9jv z@M#6{2ZQ5wDd#b+v5)Vsw&AA$>bLy#9K*TPvxoz~D%w%;7esz2I&Q2VPrCT`%Hvl| zhPW_WFPJW4=OBZrd1uS3_FoM-RL98-G6Bi;hvmcGe^N40jg&ZLWZQbX`8z|fl!n8n znfmCtbKQ$A!{bIKrH&{sX@ZB*9C8J=a|!Gyuq3BM^CAnKn|^fH51Th6(0rH^f@@Xb za0j=xUc6n$R}fNZZ)RmD;3I0&3~~thMR{b00il(YZKXf{1;a?aNr9h3;dF}1x3bSk73{{BpRoIc++JJ2xX2wLlD!qG>$Bo-cJ3bn4`frleP;y4!C*9wVLJ-83`8`_EG&RyrN{!t;Bv~q zzhi>2Ku&n>PrGSOXJ`MM8O^W(QN$}0`d?u#9cH)A>06_=ifQm4J4z>Zc=ZOV;^c;$ zIKv(w!+HTrvo;QKkvoE`D(5=y;G}8w`g^2>G76TJy$%>0oQ#;d6Y+?alLFB@Io`{`?Fm$7GNx8f(FoxglG&4qX0 zWY?3KE%-E*$4!TCn#nugAdUqgh=SYCm4sw1?k)^x{sdu)rg{Gu(rej=TJY^@(>E;% z-2|OweBHq;U)y^%OUT;u^3CLNe5V$Q9I(2(nV5n2)TrO}E#EVU`OU)Z1EmoGAndVM zjbjv~hsqKe>i6bq=hq}HOm<2=G9cV=ZutCB5(g~tG%P5u4k(e8=`-f`d%`511&i|F zKdZ4t*NBrZ9zX0==_CSGPRzKH;E#1YmznLfX0|XoXR?GFwmMxCgItWS2eJjVA&+kM z4i788>yG&V;FG;>wBArYa@?4*K(~;A5Y5dom+W6~pnWZImpAhYWyz3nAo#tp+NKH2 zyhuPFe$d+?hM~Lr1;CCamPXn#@7CTZN#+*@bs$I}Nd#9#_`+c#b`hT10an`n9sOg$$Q1AMJ2v zYB}6E4ai`=`oS^R&nL2*YY2I}Y<*g&rtt1=_%xWZ@ib(Qa+i@z5TqOm`BP*hDw4nu3`Z9bs zTpsKqHXS$o3}{bHxq3pp@fUr~)1`1g$y5AuldCn70vMCXLbUc7QP@HhwqL-j`e`#6 zWQ6hPJI)Tw>fMZvNu6E?NWL!_pd4O0jZ?F;g#vm>cCkO;sn{XwLmJ@3VE4qA?dXZ% zANC<`@MJsS$j8LPmiNhw1d77{&H(LXe#9BN5`sh^5lLc6Dc{B$S`<0nW=c-!b zv0MlO1qB5t8ca@=^jq|SWn;~{eThw?9-nFS@)C0%dF*3D56A)rj_}lj$B2(p9xV*@ zl;5Sqbp`+Vu-6}aXdcN&=#uKA?PA!%`}ywT=*X__L%`w(bo2wazrm&}`5YmIpp786 z*x~G_*@s<=_Y0dl+2T~>eMmc(=0_q_C)Hv&&@9KlQXT)DqKQ(T^c<&*`OD8i^uq0c z`QRl=q?1$gU(HN>EejH2lq87@yS`0vHo~fEsT{90UlfD>_mNi*TYNKy`)o z6P9|_K?YaYSUzF7o0HG*Mqs zj}~@UTUiO(Z21&Dn^-RWXM-#v;kc4)9#yjtP18P99I0xrYzTMizebY~vQNVZf9*#gR4D{KMIW(-CeR=2v`6wf8Zf{IU&nerZnL3QI5IlHs_tC@mIdY-avM&c@1 zm`9UXN{pQ+EI1>~JY>njR~Ct&an@8iSn%#f@;)vAl(5e79W;u)+ma1SGVKa~#eMwF zLhOYNlVoXqx24O#2q~u4zBo+JpV~$x!i8p(h%-<`Uo`d)PtOmf;cT!>Q`VsB?Qr}z zY4-mZ>c0?b|NS0o-lr>n6P5+}k8uMBsKq0ZJ&ct9>?o3;A*lKnS(LpjBGjJvzwZ2EJzsjK(Gc%e;dg1p>*i{oLHkc9VR80v*x^>Gn zn}}s!MSYRnvWUb1L00dm?`z+`lNZYps;0DMvVgEP+m?_=f;w0_Ge ziPDD$z`V>&0NM*N*q$QU_NM*IC1IjkhW0;Z+b8E7pLAB2r>sDxLNW$HizqCD(?xxK zeMLoKfJOayxBq$M(E(*`?Uam+sOk3qqOyO;#{aIH|I0!CFWr6Rl5zBZK3mp$xPD}} zql|qKb2fNMN9XLkED1QpAgFyL71F zL_Cnj;qv3h^rh=<0qePzVVLK|K#(S0U3> z`7x8l$IIQFOP4^zjsOu65ukhjUS3gA(t4Hq-*oOjNAo|9%D<^z!iCjmCIZeoD*OyOE;qf$Re_j3= zdj;bkTe^f?@3X3YAd!o($aME^{S(G(3Hil(1B2dJ(kZJZBnl>`inwt(Qc_aH=xEF< zVN8>sn4%;>AHV{1lVHQOH8#7)QLf2nkJH~v?kfVGXC|V3Nf(CjU|ufF_mq+SS0++tgL6J%p}h{&e&L-O`&*-&c8^g`Iz^X zJzCZh5y`%s9OdxzwsFgP}|H))NVHGO3V`XPZ{}5?G%bc+pB9+PAyhTUEw&X z$j@se=Z#v|E*}qf7n9XbHW9k}Jcbdjw;R_^rt^^yeUC?^FWIu z0qg|$W;z1bn^>Y1j}L6z+9Kpimm`HGd!=C6hBy#4j+>$!8>5fB7 zZNc4ML1l%j0(MLxVdl>cyO{O%6;ZO-GkBOSOU20ZNgMYRK4v9n-HhvH1T9)AE!y6K zV~$ift%r^{$ktt`6O3@qJLU*i8MWCfWbwx%RnBMtyO`7}AREN0Dgje4Psjvf@yYS)xqi8QZA*8y%3CMOW`{rw?(-~O3k{qOeX->Ym~ z(k=-iY{D*)IARw?AYSMhKi|=!nPp>hEzrJ*5w1 z#bj8}tU|yK=!Z>lKOn59mVHK{PQ4IX{9`+q_|;kAWn@HIz^c76JpF!hd3pI-AG%I5sSmm>7g{=! z73`iq%RQ9Q*iwDwtN{Uc7Q@cnQcDi#-`<2n%lKB%(s9~sEm%=vpnl3CK#$uGZFO6) zK7V6C!0t#C%KmVB2AsqBx1+P>&_P8S%fd^WdtzI&7w) zlG<>NjJfO2Fm*hsZYVS;^44$G0)Tjy|VZyZf)}~MHIbI*PdO^q#XxiI7fGV7J@!oJ8i8|?}vz@S0eC8)^ zz7NuES9}R#*+HYf7@b+axtIE53O(OLcL65~P9US(WQ^Wp>lC7elm5KqevNpCNxmm` zUy9oJWtvm~;^TXGOQR>~V>Vjs<*3Dj3A1-ygP?#u#6R0={S9q!Tz8;ttzz_mr)uZi zi5xEMZmI1xHDhF*2^@dVga08G)QmZx#k8vUS~9K2aoSk@l5WCGs zW1E|!e6g>#K>zfMq&b88Br8Ik&t1ydQg`QF6mBewa}Lt1xUkAiX$e>8!^1;R5FEh0 z`04t@=6?V2z&=60>FT^N`*n90zf<{<25sTKVtn0oqVMX|0-L|xVE8q!)279N+NeUB zOLD!1x3_m@W+t>9|Bd7XD^FnVcfsFc!ife12M71}_gmnQATDasDEzYS_#G;Ae^4vq zZ?C`SySg6%IxUdgEn_&_;U63rc;jJFjRq37gZ1;=AGLtnjDdfaI=~x&-#tA|M+34C zpKbd8m>z)`BH;p#O0_VONk0-mCVTB04s+?5Y?t1k%|85Lu!yL>W2GztwLQgi4Z1f> z#8()SRsOZG&$rY);5n@Rxg$Qp6eyT;voPLB6enGG9PbAfu6p-qT898FdwNjqL56?i z#YB=Pwll|{k^20Mc*yoAh1=2C+1XiG7yxomqgW0O;2wD$s-)zgHK3BsH2X|0$Yv1- zJ$#EnE!2mfG6Ds@I^Miq5xu*+D=97A8BX6JuT%0vHvM$)At#WmZq+jxT^KwqjP_IE zCama(;>{6a(^GnFA%7H^lFI9&Aa`p=7Z)1~3nWxjn_X(henN|{EUa{mh^N$D5 z|9sP+Qwem_r216BMcV*Xgt>h7@{$i4ns+G{65QqGC3bk1lz7X{dZfia)m}&3P&vdz zMGp=RK6d@8MrmogW#hDz2$@g43Mws@AJGT@3IK2LB8BDU zho(xkoSmF{V#!u!XSF-WXJ>D2He&xZ1(h)DWT;#T_U9W?tChFaxkE|K5$Y-~S(ME4 zCRgIazZ~LOUamiWEa{fPlPK}+37L7TwIfkT&yD|2^jN}b(%hd(Q!8#;La+mgOD;=H zVT=xtFHCwb&TaE_5-Oywso8ott#@^G6&Dwm{pBpYl;x&~3JS0K$l2R%^e#%g+(s`- zqHJbu*}e0m6BD~wYgxL#4mXVdJNPu^4+)Eqc~57WkXOg~I;FMo&HYBfkA3DOh0QtM7?gg8&|?>f?M|3RIV#zC>9&^GJX}W{dE|3OgBi(8|~m= zy27ATnJ(syfUds2{Dh0FtgNPHl44<}IogY*-rOOq1iTM2J5oH|GE>@1f58uTn%os& z6rwZ;ON8y2KcSez4g80|R}z-CHeT-&NHhbX5>=s9;i?$paKuJKN;er1i-@fuT|Q92 zfyh7~@2n&F)#xgc6sfCv`0<32J^072dA3gj1Oln4`7=5?y0p~N(BKBG*a`X?iO;-V z8v->h73zyUY?Ly&_+C@9*yc?^r8w_0E_bUk)%p0K?4(^zR~~JET~Cd-myVDK*5lb^ z`Ga9LPm3Or>bgFb{uL~lUtC&xe=(tyIygBw$zeSWw5Qv*recA@?R&nY>{N5B}ylS6?mnrNdpu#@5pAaX0}ml)>aO z`aUxF=4w&_3Rc;+Rmhl%e<5QnyRZ02#gL2}N{WlCYiN|~)>}>dP*D3J-%ve zyr&AA6VXo9AvSL!Eg9$wS%Tuh7t|@gz^xgByO;~bY(di%ksz%(yFR|a4x+C z7-UhfCmi|6p!$O-1l8~fmJnN0u!LAnyukL-y>y%4fyK;q>+!;0)tYT+Ci)^8J~%h< zL3kMake Live Triage Drive to bring up the main dialog. + +\image html live_triage_dialog.png + +Select the drive you want to use - any type of USB storage device will work. For best results use the fastest drive available. Once the process is complete the root folder will contain an Autopsy folder and a RunFromUSB.bat file. + +\section live_triage_usage Running Autopsy from the live triage drive + +Insert the drive into the target machine and browse to it in Windows Explorer. Right click on RunFromUSB.bat and select "Run as administrator". This is necessary to analyze the local drives. + +\image html live_triage_script.png + +Running the script will generate a few more directories on the USB drive. configData stores all the data used by Autopsy - primarily configuration files and temporary files. You can make changes to the Autopsy settings and they will persist between runs. The cases directory is created as a recommended place to save your case data. You will need to browse to it when creating a case in Autopsy. + +Once Autopsy is running, proceed to create a case as normal, making sure to save it on the USB drive. + +\image html live_triage_case.png + +Then choose the Local Disk data source and select the desired drive. + +\image html live_triage_ds.png + +See the \ref ds_local page for more information on local disk data sources. + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index e4a1e2b3a9..a72256c7f6 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -60,6 +60,7 @@ The following topics are available here: - \subpage windows_authentication - \subpage multiuser_sec_page - \subpage multiuser_page +- \subpage live_triage_page - \subpage advanced_page If the topic you need is not listed, refer to the Autopsy Wiki or join the SleuthKit User List at SourceForge. From 0130993b31011a657e77455c9a320e9a8b1815a9 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 24 Jan 2018 08:30:19 -0500 Subject: [PATCH 26/36] Minor text fix --- docs/doxygen-user/live_triage.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen-user/live_triage.dox b/docs/doxygen-user/live_triage.dox index f8272a1b62..d85b54a20a 100644 --- a/docs/doxygen-user/live_triage.dox +++ b/docs/doxygen-user/live_triage.dox @@ -18,7 +18,7 @@ Insert the drive into the target machine and browse to it in Windows Explorer. R \image html live_triage_script.png -Running the script will generate a few more directories on the USB drive. configData stores all the data used by Autopsy - primarily configuration files and temporary files. You can make changes to the Autopsy settings and they will persist between runs. The cases directory is created as a recommended place to save your case data. You will need to browse to it when creating a case in Autopsy. +Running the script will generate a few more directories on the USB drive. The configData directory stores all the data used by Autopsy - primarily configuration files and temporary files. You can make changes to the Autopsy settings and they will persist between runs. The cases directory is created as a recommended place to save your case data. You will need to browse to it when creating a case in Autopsy. Once Autopsy is running, proceed to create a case as normal, making sure to save it on the USB drive. From 9190b8a811b1f56d8d214294aad9476d2cbeff8c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 24 Jan 2018 11:35:12 -0500 Subject: [PATCH 27/36] Update multi user case open screenshot --- .../images/multi_user_case_select.png | Bin 44244 -> 15919 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/doxygen-user/images/multi_user_case_select.png b/docs/doxygen-user/images/multi_user_case_select.png index f87baa555a20ec519a2ea90c7521900812f4a25d..c4ca377fc6572917d4e321491a2db8a560f068f0 100644 GIT binary patch literal 15919 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 literal 44244 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 Date: Thu, 25 Jan 2018 11:23:13 -0500 Subject: [PATCH 28/36] Fix correlation data source argument order --- .../autopsy/centralrepository/datamodel/AbstractSqlEamDb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 2cac9b4927..87478529f7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -2172,7 +2172,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } CorrelationAttributeInstance eamArtifactInstance = new CorrelationAttributeInstance( new CorrelationCase(resultSet.getInt("case_id"), resultSet.getString("case_uid"), resultSet.getString("case_name")), - new CorrelationDataSource(-1, resultSet.getInt("case_id"), resultSet.getString("device_id"), resultSet.getString("name")), + new CorrelationDataSource(resultSet.getInt("case_id"), -1, resultSet.getString("device_id"), resultSet.getString("name")), resultSet.getString("file_path"), resultSet.getString("comment"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")) From 3f208e1a3b7d27a46a88814e3c8b3adb42222709 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Mon, 29 Jan 2018 16:10:33 -0500 Subject: [PATCH 29/36] Handling multiple files for kws list imports. --- .../GlobalListsManagementPanel.java | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java index aebf606380..6aae71be33 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java @@ -365,81 +365,84 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa chooser.addChoosableFileFilter(autopsyFilter); chooser.addChoosableFileFilter(encaseFilter); chooser.setAcceptAllFileFilterUsed(false); + chooser.setMultiSelectionEnabled(true); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); String listName = null; int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { - File selFile = chooser.getSelectedFile(); - if (selFile == null) { - return; - } + File[] selFiles = chooser.getSelectedFiles(); - //force append extension if not given - String fileAbs = selFile.getAbsolutePath(); - - final KeywordSearchList reader; - - if (KeywordSearchUtil.isXMLList(fileAbs)) { - reader = new XmlKeywordSearchList(fileAbs); - } else { - reader = new EnCaseKeywordSearchList(fileAbs); - } - - if (!reader.load()) { - KeywordSearchUtil.displayDialog( - NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.importListFileDialogMsg", fileAbs), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); - return; - } - - List toImport = reader.getListsL(); - List toImportConfirmed = new ArrayList<>(); - - final XmlKeywordSearchList writer = XmlKeywordSearchList.getCurrent(); - - for (KeywordList list : toImport) { - //check name collisions - listName = list.getName(); - if (writer.listExists(listName)) { - String[] options; - if (toImport.size() == 1) { //only give them cancel and yes buttons for single list imports - options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), - NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; - } else { - options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), - NbBundle.getMessage(this.getClass(), "KeywordSearch.noSkipMsg"), - NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; - } - int choice = JOptionPane.showOptionDialog(this, - NbBundle.getMessage(this.getClass(), "KeywordSearch.overwriteListPrompt", listName), - NbBundle.getMessage(this.getClass(), "KeywordSearch.importOwConflict"), - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); - if (choice == JOptionPane.OK_OPTION) { - toImportConfirmed.add(list); - } else if (choice == JOptionPane.CANCEL_OPTION) { - break; - } + for (File file : selFiles) { + if (file == null) { + continue; + } + + //force append extension if not given + String fileAbs = file.getAbsolutePath(); + final KeywordSearchList reader; + if (KeywordSearchUtil.isXMLList(fileAbs)) { + reader = new XmlKeywordSearchList(fileAbs); } else { - //no conflict - toImportConfirmed.add(list); + reader = new EnCaseKeywordSearchList(fileAbs); } - } + if (!reader.load()) { + KeywordSearchUtil.displayDialog( + NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.importListFileDialogMsg", fileAbs), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + return; + } - if (toImportConfirmed.isEmpty()) { - return; - } + List toImport = reader.getListsL(); + List toImportConfirmed = new ArrayList<>(); - if (!writer.writeLists(toImportConfirmed)) { - KeywordSearchUtil.displayDialog( - NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.kwListFailImportMsg"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO); + final XmlKeywordSearchList writer = XmlKeywordSearchList.getCurrent(); + + for (KeywordList list : toImport) { + //check name collisions + listName = list.getName(); + if (writer.listExists(listName)) { + String[] options; + if (toImport.size() == 1) { //only give them cancel and yes buttons for single list imports + options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), + NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; + } else { + options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), + NbBundle.getMessage(this.getClass(), "KeywordSearch.noSkipMsg"), + NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; + } + int choice = JOptionPane.showOptionDialog(this, + NbBundle.getMessage(this.getClass(), "KeywordSearch.overwriteListPrompt", listName), + NbBundle.getMessage(this.getClass(), "KeywordSearch.importOwConflict"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + if (choice == JOptionPane.OK_OPTION) { + toImportConfirmed.add(list); + } else if (choice == JOptionPane.CANCEL_OPTION) { + break; + } + + } else { + //no conflict + toImportConfirmed.add(list); + } + + } + + if (toImportConfirmed.isEmpty()) { + return; + } + + if (!writer.writeLists(toImportConfirmed)) { + KeywordSearchUtil.displayDialog( + NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.kwListFailImportMsg"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO); + } + ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_KEYWORD_LIST_PATH_KEY, file.getParent()); } - ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_KEYWORD_LIST_PATH_KEY, selFile.getParent()); } tableModel.resync(); From 8bd4317f94b35a7bb9ec83d2fb8461cd5ed19a38 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 30 Jan 2018 16:57:01 -0500 Subject: [PATCH 30/36] Add NamedThreadFactory to ThreadUtils --- .../autopsy/coreutils/ThreadUtils.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java index b9873469e7..8a354a531a 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.coreutils; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /* @@ -50,6 +51,35 @@ final public class ThreadUtils { } } + /** + * A thread factory that allows for the creation of distinctly named task + * threads by an ExecutorService constructed using an Executors factory + * method. Each thread created using the factory will be named using the + * thread name and a numerical suffix. + */ + public static class NamedThreadFactory implements ThreadFactory { + + private final String threadName; + + /** + * Contructs a thread factory that allows for the creation of distinctly + * named task threads by an ExecutorService constructed using an + * Executors factory method. Each thread created using the factory will + * be named using the thread name and a numerical suffix. + * + * @param threadName The name of the threads. + */ + public NamedThreadFactory(String threadName) { + this.threadName = threadName; + } + + @Override + public Thread newThread(Runnable task) { + return new Thread(task, threadName); + } + + } + private ThreadUtils() { } } From 52cd06a1147d5aed1dec1b6a8b06d429917e0d5e Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 30 Jan 2018 17:23:48 -0500 Subject: [PATCH 31/36] Revert "Add NamedThreadFactory to ThreadUtils" This reverts commit 8bd4317f94b35a7bb9ec83d2fb8461cd5ed19a38. --- .../autopsy/coreutils/ThreadUtils.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java index 8a354a531a..b9873469e7 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.coreutils; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /* @@ -51,35 +50,6 @@ final public class ThreadUtils { } } - /** - * A thread factory that allows for the creation of distinctly named task - * threads by an ExecutorService constructed using an Executors factory - * method. Each thread created using the factory will be named using the - * thread name and a numerical suffix. - */ - public static class NamedThreadFactory implements ThreadFactory { - - private final String threadName; - - /** - * Contructs a thread factory that allows for the creation of distinctly - * named task threads by an ExecutorService constructed using an - * Executors factory method. Each thread created using the factory will - * be named using the thread name and a numerical suffix. - * - * @param threadName The name of the threads. - */ - public NamedThreadFactory(String threadName) { - this.threadName = threadName; - } - - @Override - public Thread newThread(Runnable task) { - return new Thread(task, threadName); - } - - } - private ThreadUtils() { } } From 49e0c8d28c12fde63b101cb7e1f33a3a713bfee0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 1 Feb 2018 09:18:39 -0500 Subject: [PATCH 32/36] Remove Details button from Ingest History screenshot --- .../images/case-properties-history-tab.PNG | Bin 29219 -> 28533 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/doxygen-user/images/case-properties-history-tab.PNG b/docs/doxygen-user/images/case-properties-history-tab.PNG index fc0a4da441bf5cfce71ddfab700066e8b2196994..0a5d8f25b75fbc102bb21cbe921c518f4417e6de 100644 GIT binary patch delta 8454 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 Date: Thu, 1 Feb 2018 09:25:49 -0500 Subject: [PATCH 33/36] Merge pull request #3395 from zhhl/3366-EditDetailsButtonMisplaced 3366 Edit Details button is misplaced --- .../sleuthkit/autopsy/casemodule/CaseInformationPanel.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index b03a17920e..2a460d5c46 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,6 +61,11 @@ class CaseInformationPanel extends javax.swing.JPanel { @Override public void stateChanged(ChangeEvent e) { tabbedPane.getSelectedComponent().setSize(tabbedPane.getSelectedComponent().getPreferredSize()); + if (tabbedPane.getSelectedComponent() instanceof CasePropertiesPanel) { + editDetailsButton.setVisible(true); + } else { + editDetailsButton.setVisible(false); + } } }); } From 4c11cb8acb1fa387936dc5beaefb8f1ba85966f3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 1 Feb 2018 14:24:27 -0500 Subject: [PATCH 34/36] Merge pull request #3392 from zhhl/3469_3470-FixExcelAndHMTLReportIssues 3469 3470 fix Excel and HTML report issues --- .../sleuthkit/autopsy/report/ReportExcel.java | 17 ++++++++++++++++- .../sleuthkit/autopsy/report/ReportHTML.java | 4 +++- .../autopsy/report/TableReportGenerator.java | 6 ++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index 19526a0a19..9158790ad6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -34,6 +34,7 @@ class ReportExcel implements TableReportModule { private static final Logger logger = Logger.getLogger(ReportExcel.class.getName()); private static ReportExcel instance; + private static final int EXCEL_CELL_MAXIMUM_SIZE = 36767; //Specified at:https://poi.apache.org/apidocs/org/apache/poi/ss/SpreadsheetVersion.html private Workbook wb; private Sheet sheet; @@ -236,10 +237,24 @@ class ReportExcel implements TableReportModule { * @param row cells to add */ @Override + @NbBundle.Messages({ + "ReportExcel.exceptionMessage.dataTooLarge=Value is too long to fit into an Excel cell. ", + "ReportExcel.exceptionMessage.errorText=Error showing data into an Excel cell." + }) + public void addRow(List rowData) { Row row = sheet.createRow(rowIndex); for (int i = 0; i < rowData.size(); ++i) { - row.createCell(i).setCellValue(rowData.get(i)); + Cell excelCell = row.createCell(i); + try { + excelCell.setCellValue(rowData.get(i)); + } catch (Exception e) { + if (e instanceof java.lang.IllegalArgumentException && rowData.get(i).length() > EXCEL_CELL_MAXIMUM_SIZE) { + excelCell.setCellValue(Bundle.ReportExcel_exceptionMessage_dataTooLarge() + e.getMessage()); + } else { + excelCell.setCellValue(Bundle.ReportExcel_exceptionMessage_errorText()); + } + } } ++rowIndex; } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 88d9fe44b1..01a179a370 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -51,6 +51,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; @@ -554,7 +555,8 @@ class ReportHTML implements TableReportModule { StringBuilder builder = new StringBuilder(); builder.append("\t\n"); //NON-NLS for (String cell : row) { - builder.append("\t\t").append(cell).append("\n"); //NON-NLS + String escapeHTMLCell = EscapeUtil.escapeHtml(cell); + builder.append("\t\t").append(escapeHTMLCell).append("\n"); //NON-NLS } builder.append("\t\n"); //NON-NLS rowCount++; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 93894022b3..499f848d54 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,6 @@ import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; -import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; @@ -666,8 +665,7 @@ class TableReportGenerator { tableModule.startTable(columnHeaderNames); } - String previewreplace = EscapeUtil.escapeHtml(preview); - tableModule.addRow(Arrays.asList(new String[]{previewreplace.replaceAll(" Date: Fri, 2 Feb 2018 11:56:04 -0500 Subject: [PATCH 35/36] 3522 change case path used by Multi-User case panel to be real path instead of all caps --- .../src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 24aaf70d2e..e283ef91d9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -29,6 +29,8 @@ import org.netbeans.swing.outline.Outline; import org.openide.nodes.Node; import java.awt.EventQueue; import java.io.File; +import java.io.IOException; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -222,10 +224,10 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider String name = file.getName().toLowerCase(); if (autFilePath == null && name.endsWith(".aut")) { try { - caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); - } catch (CaseMetadata.CaseMetadataException ex) { + caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()).toRealPath(LinkOption.NOFOLLOW_LINKS))); + } catch (CaseMetadata.CaseMetadataException | IOException ex) { LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } + } break; } } From 703cb9f223fd378402e29d33befcb035c23f75ec Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Feb 2018 12:47:32 -0500 Subject: [PATCH 36/36] 3522 fix location of real path conversion to before any file access --- .../autopsy/casemodule/CaseBrowser.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index e283ef91d9..4f25030fd3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -209,28 +209,34 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider 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' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - continue; - } - String autFilePath = null; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(".aut")) { - try { - caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()).toRealPath(LinkOption.NOFOLLOW_LINKS))); - } catch (CaseMetadata.CaseMetadataException | IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } - break; + Path casePath; + try { + casePath = Paths.get(node).toRealPath(LinkOption.NOFOLLOW_LINKS); + + File caseFolder = casePath.toFile(); + if (caseFolder.exists()) { + /* + * Search for '*.aut' files. + */ + File[] fileArray = caseFolder.listFiles(); + if (fileArray == null) { + continue; + } + String autFilePath = null; + for (File file : fileArray) { + String name = file.getName().toLowerCase(); + if (autFilePath == null && name.endsWith(".aut")) { + try { + caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); + } + break; + } } } + } catch (IOException ignore) { + //if a path could not be resolved to a real path do add it to the caseList } } return caseList;