Interim check in of files for improved multiuser case dialog

This commit is contained in:
Richard Cordovano 2019-01-15 12:47:42 -05:00
parent eca1472d5b
commit ac4637bf8c
17 changed files with 1016 additions and 771 deletions

View File

@ -193,14 +193,10 @@ ReviewModeCasePanel.MetadataFileHeaderText=Metadata File
OpenMultiUserCasePanel.jLabel1.text=Recent Cases
OpenMultiUserCasePanel.openButton.text=Open
OpenMultiUserCasePanel.cancelButton.text=Cancel
MultiUserCasesPanel.bnOpen.text=&Open
CueBannerPanel.newCaseLabel.text=New Case
CueBannerPanel.openCaseButton.text=
CueBannerPanel.openCaseLabel.text=Open Case
MultiUserCasesPanel.bnOpenSingleUserCase.text=Open Single-User Case...
CueBannerPanel.newCaseButton.text=
MultiUserCasesPanel.searchLabel.text=Select any case and start typing to search by case name
MultiUserCasesPanel.cancelButton.text=Cancel
ImageFilePanel.sectorSizeLabel.text=Sector size:
LocalDiskPanel.sectorSizeLabel.text=Sector Size:
LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default
@ -241,3 +237,6 @@ ImageFilePanel.md5HashTextField.text=
ImageFilePanel.errorLabel.text=Error Label
ImageFilePanel.hashValuesNoteLabel.text=NOTE: These values will not be validated when the data source is added.
ImageFilePanel.hashValuesLabel.text=Hash Values (optional):
OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name
OpenMultiUserCasePanel.bnOpenSingleUserCase.text=Open Single-User Case...

View File

@ -32,6 +32,7 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
@ -57,6 +58,7 @@ import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
@ -80,6 +82,7 @@ import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.services.Services;
import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchAction;
import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction;
import org.sleuthkit.autopsy.coordinationservice.CaseNodeData;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
@ -1956,9 +1959,9 @@ public class Case {
}
if (isNewCase) {
createCaseData(progressIndicator);
createCaseData(progressIndicator); // RJCTODO: This name is vague
} else {
openCaseData(progressIndicator);
openCaseData(progressIndicator); // RJCTODO: This name is vague
}
if (Thread.currentThread().isInterrupted()) {
@ -1970,6 +1973,7 @@ public class Case {
if (Thread.currentThread().isInterrupted()) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
}
} catch (CaseActionException ex) {
/*
* Cancellation or failure. Clean up. The sleep is a little hack to
@ -2003,6 +2007,19 @@ public class Case {
"Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
})
private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
// RJCTODO: progress
try {
CoordinationService coordinationService = CoordinationService.getInstance();
CaseNodeData nodeData = new CaseNodeData(metadata);
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
} catch (CoordinationServiceException | InterruptedException | ParseException ex) {
// RJCTODO
}
if (Thread.currentThread().isInterrupted()) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
}
/*
* Create the case directory, if it does not already exist.
*
@ -2014,6 +2031,10 @@ public class Case {
Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
}
if (Thread.currentThread().isInterrupted()) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
}
/*
* Create the case database.
*/
@ -2065,6 +2086,24 @@ public class Case {
})
private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
try {
if (Thread.currentThread().isInterrupted()) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
}
// RJCTODO: progress
try {
CoordinationService coordinationService = CoordinationService.getInstance();
CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
nodeData.setLastAccessDate(new Date());
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
} catch (CoordinationServiceException | InterruptedException | CaseNodeData.InvalidDataException ex) {
// RJCTODO
}
if (Thread.currentThread().isInterrupted()) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
}
progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
String databaseName = metadata.getCaseDatabaseName();
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {

View File

@ -1,265 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.lang.reflect.InvocationTargetException;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumnModel;
import org.netbeans.swing.etable.ETableColumn;
import org.netbeans.swing.etable.ETableColumnModel;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.openide.nodes.Node;
import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.openide.explorer.ExplorerManager;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
/**
* A Swing JPanel with a scroll pane child component. The scroll pane contain
* the table of cases.
*
* Used to display a list of multi user cases and allow the user to open one of
* them.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider {
private static final long serialVersionUID = 1L;
private final Outline outline;
private ExplorerManager em;
private final org.openide.explorer.view.OutlineView outlineView;
private int originalPathColumnIndex = 0;
private static final Logger LOGGER = Logger.getLogger(CaseBrowser.class.getName());
private LoadCaseListWorker tableWorker;
@Override
public ExplorerManager getExplorerManager() {
return em;
}
/**
* Creates a new CaseBrowser
*/
CaseBrowser() {
outlineView = new org.openide.explorer.view.OutlineView();
initComponents();
outline = outlineView.getOutline();
outlineView.setPropertyColumns(
Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(),
Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath());
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name());
customize();
}
/**
* Configures the the table of cases and its columns.
*/
private void customize() {
outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
TableColumnModel columnModel = outline.getColumnModel();
int dateColumnIndex = 0;
for (int index = 0; index < columnModel.getColumnCount(); index++) {
//get indexes for created date column and path column
if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_metadataFilePath())) {
originalPathColumnIndex = index;
} else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_createdTime())) {
dateColumnIndex = index;
}
}
//Hide path column by default (user can unhide it)
ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex);
((ETableColumnModel) columnModel).setColumnHidden(column, true);
outline.setRootVisible(false);
//Sort on Created date column in descending order by default
outline.setColumnSorted(dateColumnIndex, false, 1);
if (null == em) {
em = new ExplorerManager();
}
caseTableScrollPane.setViewportView(outlineView);
this.setVisible(true);
outline.setRowSelectionAllowed(false);
}
/**
* Add a listener to changes in case selections in the table
*
* @param listener the ListSelectionListener to add
*/
void addListSelectionListener(ListSelectionListener listener) {
outline.getSelectionModel().addListSelectionListener(listener);
}
/**
* Get the path to the .aut file for the selected case.
*
* @return the full path to the selected case's .aut file
*/
String getCasePath() {
int[] selectedRows = outline.getSelectedRows();
if (selectedRows.length == 1) {
try {
return ((Node.Property) outline.getModel().getValueAt(outline.convertRowIndexToModel(selectedRows[0]), originalPathColumnIndex)).getValue().toString();
} catch (IllegalAccessException | InvocationTargetException ex) {
LOGGER.log(Level.SEVERE, "Unable to get case path from table.", ex);
}
}
return null;
}
/**
* Check if a row could be and is selected.
*
* @return true if a row is selected, false if no row is selected
*/
boolean isRowSelected() {
return outline.getRowSelectionAllowed() && outline.getSelectedRows().length > 0;
}
@NbBundle.Messages({"CaseBrowser.caseListLoading.message=Please Wait..."})
/**
* Gets the list of cases known to the review mode cases manager and
* refreshes the cases table.
*/
void refresh() {
if (tableWorker == null || tableWorker.isDone()) {
outline.setRowSelectionAllowed(false);
//create a new TableWorker to and execute it in a background thread if one is not currently working
//set the table to display text informing the user that the list is being retreived and disable case selection
EmptyNode emptyNode = new EmptyNode(Bundle.CaseBrowser_caseListLoading_message());
em.setRootContext(emptyNode);
tableWorker = new LoadCaseListWorker();
tableWorker.execute();
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
caseTableScrollPane = new javax.swing.JScrollPane();
setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 5));
setLayout(new java.awt.BorderLayout());
caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5));
caseTableScrollPane.setOpaque(false);
caseTableScrollPane.setPreferredSize(new java.awt.Dimension(5, 5));
add(caseTableScrollPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane caseTableScrollPane;
// End of variables declaration//GEN-END:variables
/**
* Swingworker to fetch the updated List of cases in a background thread
*/
private class LoadCaseListWorker extends SwingWorker<Void, Void> {
private List<CaseMetadata> cases;
/**
* Gets a list of the cases in the top level case folder
*
* @return List of cases.
*
* @throws CoordinationServiceException
*/
private List<CaseMetadata> getCases() throws CoordinationService.CoordinationServiceException {
List<CaseMetadata> caseList = new ArrayList<>();
List<String> nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES);
for (String node : nodeList) {
Path casePath;
try {
casePath = Paths.get(node).toRealPath(LinkOption.NOFOLLOW_LINKS);
File caseFolder = casePath.toFile();
if (caseFolder.exists()) {
/*
* Search for '*.aut' files.
*/
File[] fileArray = caseFolder.listFiles();
if (fileArray == null) {
continue;
}
String autFilePath = null;
for (File file : fileArray) {
String name = file.getName().toLowerCase();
if (autFilePath == null && name.endsWith(".aut")) {
try {
caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath())));
} catch (CaseMetadata.CaseMetadataException ex) {
LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex);
}
break;
}
}
}
} catch (IOException ignore) {
//if a path could not be resolved to a real path do add it to the caseList
}
}
return caseList;
}
@Override
protected Void doInBackground() throws Exception {
try {
cases = getCases();
} catch (CoordinationService.CoordinationServiceException ex) {
LOGGER.log(Level.SEVERE, "Unexpected exception while refreshing the table.", ex); //NON-NLS
}
return null;
}
@Override
protected void done() {
EventQueue.invokeLater(() -> {
MultiUserNode caseListNode = new MultiUserNode(cases);
em.setRootContext(caseListNode);
outline.setRowSelectionAllowed(true);
});
}
}
}

View File

@ -52,7 +52,8 @@ import org.xml.sax.SAXException;
public final class CaseMetadata {
private static final String FILE_EXTENSION = ".aut";
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)");
private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss (z)";
private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING);
/*
* Fields from schema version 1
@ -92,6 +93,7 @@ public final class CaseMetadata {
private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS
private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
/*
* Unread fields, regenerated on save.
*/
@ -119,6 +121,15 @@ public final class CaseMetadata {
return FILE_EXTENSION;
}
/**
* Gets the date format used for dates in case metadata.
*
* @return The date format.
*/
public static DateFormat getDateFormat() {
return new SimpleDateFormat(DATE_FORMAT_STRING);
}
/**
* Constructs a CaseMetadata object for a new case. The metadata is not
* persisted to the case metadata file until writeFile or a setX method is
@ -295,7 +306,7 @@ public final class CaseMetadata {
*
* @return The date this case was created, as a string.
*/
String getCreatedDate() {
public String getCreatedDate() {
return createdDate;
}

View File

@ -161,7 +161,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (multiUserCaseWindow == null) {
multiUserCaseWindow = MultiUserCasesDialog.getInstance();
multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance();
}
multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
multiUserCaseWindow.setVisible(true);

View File

@ -0,0 +1,198 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coordinationservice.CaseNodeData;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
/**
* A NetBeans Explorer View node that represents a multi-user case.
*/
final class MultiUserCaseNode extends AbstractNode {
private static final Logger logger = Logger.getLogger(MultiUserCaseNode.class.getName());
private static final String CASE_AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt";
private final CaseNodeData caseNodeData;
private final Path caseMetadataFilePath;
private final Path caseAutoIngestLogFilePath;
/**
* Constructs a NetBeans Explorer View node that represents a multi-user
* case.
*
* @param caseNodeData The coordination service node data for the case.
*/
MultiUserCaseNode(CaseNodeData caseNodeData) {
super(Children.LEAF);
super.setName(caseNodeData.getDisplayName());
setName(caseNodeData.getDisplayName());
setDisplayName(caseNodeData.getDisplayName());
this.caseNodeData = caseNodeData;
this.caseMetadataFilePath = Paths.get(caseNodeData.getDirectory().toString(), caseNodeData.getName() + CaseMetadata.getFileExtension());
this.caseAutoIngestLogFilePath = Paths.get(caseNodeData.getDirectory().toString(), CASE_AUTO_INGEST_LOG_FILE_NAME);
}
@NbBundle.Messages({
"CaseNode.column.name=Name",
"CaseNode.column.createTime=Create Time",
"CaseNode.column.path=Path"
})
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(),
Bundle.CaseNode_column_name(),
Bundle.CaseNode_column_name(),
caseNodeData.getDisplayName()));
sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createTime(),
Bundle.CaseNode_column_createTime(),
Bundle.CaseNode_column_createTime(),
caseNodeData.getCreateDate()));
sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_path(),
Bundle.CaseNode_column_path(),
Bundle.CaseNode_column_path(),
caseNodeData.getDirectory().toString()));
return sheet;
}
@Override
public Action[] getActions(boolean context) {
List<Action> actions = new ArrayList<>();
actions.add(new OpenMultiUserCaseAction(this.caseMetadataFilePath));
actions.add(new OpenCaseAutoIngestLogAction(this.caseAutoIngestLogFilePath));
return actions.toArray(new Action[actions.size()]);
}
@Override
public Action getPreferredAction() {
return new OpenMultiUserCaseAction(this.caseMetadataFilePath);
}
/**
* An action that opens the specified case and hides the multi-user case
* panel.
*/
@NbBundle.Messages({
"MultiUserNode.OpenMultiUserCaseAction.menuItemText=Open Case",
"# {0} - caseErrorMessage", "MultiUserNode.OpenMultiUserCaseAction.caseOpeningErrorErrorMsg=Failed to open case: {0}"
})
private static final class OpenMultiUserCaseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final Path caseMetadataFilePath;
OpenMultiUserCaseAction(Path caseMetadataFilePath) {
super(Bundle.MultiUserNode_OpenMultiUserCaseAction_menuItemText());
this.caseMetadataFilePath = caseMetadataFilePath;
}
public void actionPerformed(ActionEvent e) {
StartupWindowProvider.getInstance().close();
OpenMultiUserCaseDialog.getInstance().setVisible(false);
new Thread(() -> {
try {
Case.openAsCurrentCase(caseMetadataFilePath.toString());
} catch (CaseActionException ex) {
if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS
/*
* CaseActionException error messages are user-friendly.
*/
MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenMultiUserCaseAction_caseOpeningErrorErrorMsg(ex.getLocalizedMessage()));
}
SwingUtilities.invokeLater(() -> {
StartupWindowProvider.getInstance().open();
OpenMultiUserCaseDialog.getInstance().setVisible(true);
});
}
}).start();
}
@Override
public Object clone() throws CloneNotSupportedException {
super.clone();
throw new CloneNotSupportedException();
}
}
/**
* An action that opens the case auto ingest log for the specified case.
*/
@NbBundle.Messages({
"MultiUserNode.OpenCaseAutoIngestLogAction.menuItemText=Open Auto Ingest Log File",
"MultiUserNode.OpenCaseAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.",
"MultiUserNode.OpenCaseAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.",
})
private static final class OpenCaseAutoIngestLogAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final Path caseAutoIngestLogFilePath;
OpenCaseAutoIngestLogAction(Path caseAutoIngestLogFilePath) {
super(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_menuItemText());
this.caseAutoIngestLogFilePath = caseAutoIngestLogFilePath;
this.setEnabled(caseAutoIngestLogFilePath.toFile().exists());
}
@Override
public void actionPerformed(ActionEvent e) {
try {
if (caseAutoIngestLogFilePath.toFile().exists()) {
Desktop.getDesktop().edit(caseAutoIngestLogFilePath.toFile());
} else {
MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_deletedLogErrorMsg());
}
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error opening case auto ingest log file at %s", caseAutoIngestLogFilePath), ex); //NON-NLS
MessageNotifyUtil.Message.error(Bundle.MultiUserNode_OpenCaseAutoIngestLogAction_logOpenFailedErrorMsg());
}
}
@Override
public Object clone() throws CloneNotSupportedException {
super.clone();
throw new CloneNotSupportedException();
}
}
}

View File

@ -0,0 +1,259 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableColumnModel;
import org.netbeans.swing.etable.ETableColumn;
import org.netbeans.swing.etable.ETableColumnModel;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.openide.explorer.ExplorerManager;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coordinationservice.CaseNodeData;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
import java.util.concurrent.ExecutionException;
import org.openide.explorer.view.OutlineView;
/**
* A JPanel with a scroll pane child component that contains a NetBeans
* OutlineView that can be used to display a list of the multi-user cases known
* to the coordination service.
*/
@SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives
class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(MultiUserCasesBrowserPanel.class.getName());
private ExplorerManager explorerManager; // RJCTODO: COnsider making this final
private final Outline outline;
private final OutlineView outlineView;
private LoadCaseListWorker loadCaseListWorker;
/**
* Constructs a JPanel with a scroll pane child component that contains a
* NetBeans OutlineView that can be used to display a list of the multi-user
* cases known to the coordination service.
*/
MultiUserCasesBrowserPanel() {
outlineView = new org.openide.explorer.view.OutlineView();
initComponents();
outline = outlineView.getOutline();
customizeOutlineView();
}
/**
* Configures the the table of cases and its columns.
*/
private void customizeOutlineView() {
outlineView.setPropertyColumns(
Bundle.CaseNode_column_createTime(), Bundle.CaseNode_column_createTime(), // RJCTODO: Move these into this file?
Bundle.CaseNode_column_path(), Bundle.CaseNode_column_path());
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name());
outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
TableColumnModel columnModel = outline.getColumnModel();
int pathColumnIndex = 0;
int dateColumnIndex = 0;
for (int index = 0; index < columnModel.getColumnCount(); index++) {
if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_path())) {
pathColumnIndex = index;
} else if (columnModel.getColumn(index).getHeaderValue().toString().equals(Bundle.CaseNode_column_createTime())) {
dateColumnIndex = index;
}
}
/*
* Hide path column by default (user can unhide it)
*/
ETableColumn column = (ETableColumn) columnModel.getColumn(pathColumnIndex);
((ETableColumnModel) columnModel).setColumnHidden(column, true);
outline.setRootVisible(false);
/*
* Sort on Created date column in descending order by default.
*/
outline.setColumnSorted(dateColumnIndex, false, 1);
if (null == explorerManager) {
explorerManager = new ExplorerManager();
}
caseTableScrollPane.setViewportView(outlineView);
this.setVisible(true);
}
@Override
public ExplorerManager getExplorerManager() {
return explorerManager;
}
/**
* Gets the list of cases known to the review mode cases manager and
* refreshes the cases table.
*/
@NbBundle.Messages({
"MultiUserCasesBrowserPanel.waitNode.message=Please Wait..."
})
void refreshCases() {
if (loadCaseListWorker == null || loadCaseListWorker.isDone()) {
/*
* RJCTODO: Explain this or move the data fetching into the create
* keys method of the nodes...
*/
EmptyNode emptyNode = new EmptyNode(Bundle.MultiUserCasesBrowserPanel_waitNode_message());
explorerManager.setRootContext(emptyNode);
loadCaseListWorker = new LoadCaseListWorker();
loadCaseListWorker.execute();
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
caseTableScrollPane = new javax.swing.JScrollPane();
setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 5));
setLayout(new java.awt.BorderLayout());
caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5));
caseTableScrollPane.setOpaque(false);
caseTableScrollPane.setPreferredSize(new java.awt.Dimension(5, 5));
add(caseTableScrollPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane caseTableScrollPane;
// End of variables declaration//GEN-END:variables
/**
* A background task that gets the multi-user case data from the case
* directory lock coordination service nodes.
*/
private class LoadCaseListWorker extends SwingWorker<List<CaseNodeData>, Void> {
private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS
private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS
@Override
protected List<CaseNodeData> doInBackground() throws Exception {
final List<CaseNodeData> cases = new ArrayList<>();
final CoordinationService coordinationService = CoordinationService.getInstance();
final List<String> nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
for (String nodeName : nodeList) {
/*
* Ignore case name lock nodes.
*/
final Path nodeNameAsPath = Paths.get(nodeName);
if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) {
continue;
}
/*
* Ignore case auto ingest log lock nodes and resource lock
* nodes.
*/
final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString();
if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME) || lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) {
continue;
}
/*
* Get the data from the case directory lock node. This data may not exist
* for "legacy" nodes. If it is missing, create it.
*/
try {
CaseNodeData nodeData;
byte[] nodeBytes = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, nodeName);
if (nodeBytes != null && nodeBytes.length > 0) {
nodeData = new CaseNodeData(nodeBytes);
if (nodeData.getVersion() > 0) {
cases.add(nodeData);
} else {
nodeData = createNodeDataFromCaseMetadata(nodeName);
}
} else {
nodeData = createNodeDataFromCaseMetadata(nodeName);
}
cases.add(nodeData);
} catch (CoordinationService.CoordinationServiceException | CaseNodeData.InvalidDataException | IOException | CaseMetadata.CaseMetadataException ex) {
logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex);
}
}
return cases;
}
@Override
protected void done() {
try {
final List<CaseNodeData> cases = get();
EventQueue.invokeLater(() -> {
MultiUserCasesRootNode caseListNode = new MultiUserCasesRootNode(cases);
explorerManager.setRootContext(caseListNode);
});
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, "Unexpected interrupt during background processing", ex);
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, "Error during background processing", ex);
}
}
private CaseNodeData createNodeDataFromCaseMetadata(String nodeName) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException {
Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS);
File caseDirectory = caseDirectoryPath.toFile();
if (caseDirectory.exists()) {
File[] files = caseDirectory.listFiles();
for (File file : files) {
String name = file.getName().toLowerCase();
if (name.endsWith(CaseMetadata.getFileExtension())) {
CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath()));
CaseNodeData nodeData = new CaseNodeData(metadata);
CoordinationService coordinationService = CoordinationService.getInstance();
coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray());
}
}
}
throw new IOException(String.format("Could not find case metadata file for %s", nodeName));
}
}
}

View File

@ -1,90 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Dialog;
import java.awt.event.KeyEvent;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.KeyStroke;
import org.openide.windows.WindowManager;
/**
* This class extends a JDialog and maintains the MultiUserCasesPanel.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class MultiUserCasesDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static final String REVIEW_MODE_TITLE = "Open Multi-User Case";
private static MultiUserCasesPanel multiUserCasesPanel;
private static MultiUserCasesDialog instance;
/**
* Gets the instance of the MultiuserCasesDialog.
*
* @return The instance.
*/
static public MultiUserCasesDialog getInstance() {
if(instance == null) {
instance = new MultiUserCasesDialog();
instance.init();
}
return instance;
}
/**
* Constructs a MultiUserCasesDialog object.
*/
private MultiUserCasesDialog() {
super(WindowManager.getDefault().getMainWindow(),
REVIEW_MODE_TITLE,
Dialog.ModalityType.APPLICATION_MODAL);
}
/**
* Initializes the multi-user cases panel.
*/
private void init() {
getRootPane().registerKeyboardAction(
e -> {
setVisible(false);
},
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
multiUserCasesPanel = new MultiUserCasesPanel(this);
add(multiUserCasesPanel);
pack();
setResizable(false);
}
/**
* Set the dialog visibility. When setting it to visible, the contents will
* refresh.
*
* @param value True or false.
*/
@Override
public void setVisible(boolean value) {
if(value) {
multiUserCasesPanel.refresh();
}
super.setVisible(value);
}
}

View File

@ -0,0 +1,69 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.util.List;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coordinationservice.CaseNodeData;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* A root node for displaying MultiUserCaseNodes in a NetBeans Explorer View.
*/
final class MultiUserCasesRootNode extends AbstractNode {
/**
* Constructs a root node for displaying MultiUserCaseNodes in a NetBeans
* Explorer View.
*
* @param case A list of coordination service node data objects representing
* multi-user cases.
*/
MultiUserCasesRootNode(List<CaseNodeData> cases) {
super(Children.create(new MultiUserCasesRootNodeChildren(cases), true));
}
private static class MultiUserCasesRootNodeChildren extends ChildFactory<CaseNodeData> {
private final List<CaseNodeData> cases;
MultiUserCasesRootNodeChildren(List<CaseNodeData> cases) {
this.cases = cases;
}
@Override
protected boolean createKeys(List<CaseNodeData> keys) {
if (cases != null && cases.size() > 0) {
keys.addAll(cases);
}
return true;
}
@Override
protected Node createNodeForKey(CaseNodeData key) {
return new MultiUserCaseNode(key);
}
}
}

View File

@ -1,232 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
/**
* A root node containing child nodes of the multi user cases
*/
final class MultiUserNode extends AbstractNode {
@Messages({"CaseNode.column.name=Name",
"CaseNode.column.createdTime=Created Time",
"CaseNode.column.metadataFilePath=Path"})
private static final Logger LOGGER = Logger.getLogger(MultiUserNode.class.getName());
private static final String LOG_FILE_NAME = "auto_ingest_log.txt";
/**
* Provides a root node with children which each represent a case.
*
* @param caseList the list of CaseMetadata objects representing the cases
*/
MultiUserNode(List<CaseMetadata> caseList) {
super(Children.create(new MultiUserNodeChildren(caseList), true));
}
static class MultiUserNodeChildren extends ChildFactory<CaseMetadata> {
private final List<CaseMetadata> caseList;
MultiUserNodeChildren(List<CaseMetadata> caseList) {
this.caseList = caseList;
}
@Override
protected boolean createKeys(List<CaseMetadata> list) {
if (caseList != null && caseList.size() > 0) {
list.addAll(caseList);
}
return true;
}
@Override
protected Node createNodeForKey(CaseMetadata key) {
return new MultiUserCaseNode(key);
}
}
/**
* A node which represents a single multi user case.
*/
static final class MultiUserCaseNode extends AbstractNode {
private final String caseName;
private final String caseCreatedDate;
private final String caseMetadataFilePath;
private final Path caseLogFilePath;
MultiUserCaseNode(CaseMetadata multiUserCase) {
super(Children.LEAF);
caseName = multiUserCase.getCaseDisplayName();
caseCreatedDate = multiUserCase.getCreatedDate();
super.setName(caseName);
setName(caseName);
setDisplayName(caseName);
caseMetadataFilePath = multiUserCase.getFilePath().toString();
caseLogFilePath = Paths.get(multiUserCase.getCaseDirectory(), LOG_FILE_NAME);
}
/**
* Returns action to open the Case represented by this node
* @return an action which will open the current case
*/
@Override
public Action getPreferredAction() {
return new OpenMultiUserCaseAction(caseMetadataFilePath);
}
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(),
caseName));
sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(),
caseCreatedDate));
sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(),
caseMetadataFilePath));
return sheet;
}
@Override
public Action[] getActions(boolean context) {
List<Action> actions = new ArrayList<>();
actions.add(new OpenMultiUserCaseAction(caseMetadataFilePath)); //open case context menu option
actions.add(new OpenCaseLogAction(caseLogFilePath));
return actions.toArray(new Action[actions.size()]);
}
}
@Messages({"MultiUserNode.OpenMultiUserCaseAction.text=Open Case"})
/**
* An action that opens the specified case and hides the multi user case
* panel.
*/
private static final class OpenMultiUserCaseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final String caseMetadataFilePath;
OpenMultiUserCaseAction(String path) {
super(Bundle.MultiUserNode_OpenMultiUserCaseAction_text());
caseMetadataFilePath = path;
}
@Override
public void actionPerformed(ActionEvent e) {
StartupWindowProvider.getInstance().close();
MultiUserCasesDialog.getInstance().setVisible(false);
new Thread(
() -> {
try {
Case.openAsCurrentCase(caseMetadataFilePath);
} catch (CaseActionException ex) {
if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS
MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage());
}
SwingUtilities.invokeLater(() -> {
//GUI changes done back on the EDT
StartupWindowProvider.getInstance().open();
MultiUserCasesDialog.getInstance().setVisible(true);
});
}
}
).start();
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@Messages({"MultiUserNode.OpenCaseLogAction.text=Open Log File"})
/**
* An action that opens the specified case and hides the multi user case
* panel.
*/
private static final class OpenCaseLogAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final Path pathToLog;
OpenCaseLogAction(Path caseLogFilePath) {
super(Bundle.MultiUserNode_OpenCaseLogAction_text());
pathToLog = caseLogFilePath;
this.setEnabled(caseLogFilePath != null && caseLogFilePath.toFile().exists());
}
@Override
public void actionPerformed(ActionEvent e) {
if (pathToLog != null) {
try {
if (pathToLog.toFile().exists()) {
Desktop.getDesktop().edit(pathToLog.toFile());
} else {
JOptionPane.showMessageDialog(MultiUserCasesDialog.getInstance(), org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotFindLog"),
org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.unableToShowLogFile"), JOptionPane.ERROR_MESSAGE);
}
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, String.format("Error attempting to open case auto ingest log file %s", pathToLog), ex);
JOptionPane.showMessageDialog(MultiUserCasesDialog.getInstance(),
org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.cannotOpenLog"),
org.openide.util.NbBundle.getMessage(MultiUserNode.class, "DisplayLogDialog.unableToShowLogFile"),
JOptionPane.PLAIN_MESSAGE);
}
}
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Dialog;
import java.awt.event.KeyEvent;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.KeyStroke;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
/**
* A singleton JDialog that allows a user to open a multi-user case.
*/
final class OpenMultiUserCaseDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static OpenMultiUserCaseDialog instance;
private static OpenMultiUserCasePanel multiUserCasesPanel;
/**
* Gets the singleton JDialog singleton JDialog that allows a user to open a
* multi-user case.
*
* @return The singleton JDialog instance.
*/
public synchronized static OpenMultiUserCaseDialog getInstance() {
if (instance == null) {
instance = new OpenMultiUserCaseDialog();
instance.init();
}
return instance;
}
/**
* Constructs a singleton JDialog singleton JDialog that allows a user to
* open a multi-user case.
*/
@NbBundle.Messages({
"OpenMultiUserCaseDialog.title=Open Multi-User Case"
})
private OpenMultiUserCaseDialog() {
super(WindowManager.getDefault().getMainWindow(),
Bundle.OpenMultiUserCaseDialog_title(),
Dialog.ModalityType.APPLICATION_MODAL);
}
/**
* Registers a keyboard action to hide the dialog when the escape key is
* pressed and adds a OpenMultiUserCasePanel child component.
*/
private void init() {
getRootPane().registerKeyboardAction(
action -> {
setVisible(false);
},
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
multiUserCasesPanel = new OpenMultiUserCasePanel(this);
add(multiUserCasesPanel);
pack();
setResizable(false);
}
/**
* Sets the dialog visibility. When made visible, the dialog refreshes the
* display of its OpenMultiUserCasePanel child component.
*
* @param value True or false.
*/
@Override
public void setVisible(boolean value) {
if (value) {
multiUserCasesPanel.refreshDisplay();
}
super.setVisible(value);
}
}

View File

@ -28,12 +28,10 @@
<Component id="caseExplorerScrollPane" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Component id="searchLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace pref="350" max="32767" attributes="0"/>
<Component id="bnOpenSingleUserCase" min="-2" pref="192" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="190" max="-2" attributes="0"/>
<Component id="bnOpen" linkSize="1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cancelButton" linkSize="1" min="-2" max="-2" attributes="0"/>
<Component id="cancelButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
@ -48,7 +46,6 @@
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="cancelButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="bnOpen" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="bnOpenSingleUserCase" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="searchLabel" alignment="3" max="32767" attributes="0"/>
</Group>
@ -58,30 +55,10 @@
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="bnOpen">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.bnOpen.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[80, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[80, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[80, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnOpenActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="bnOpenSingleUserCase">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.bnOpenSingleUserCase.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.bnOpenSingleUserCase.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[156, 23]"/>
@ -97,7 +74,7 @@
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[80, 23]"/>
@ -116,7 +93,7 @@
<Component class="javax.swing.JLabel" name="searchLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="MultiUserCasesPanel.searchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="OpenMultiUserCasePanel.searchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,107 +18,52 @@
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Cursor;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Level;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
/**
* A panel that allows a user to open cases created by auto ingest.
* A JPanel that allows a user to open a multi-user case.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class MultiUserCasesPanel extends JPanel{
@SuppressWarnings("PMD.SingularField") // Matisse-generated UI widgets cause lots of false positives
final class OpenMultiUserCasePanel extends JPanel {
private static final Logger logger = Logger.getLogger(MultiUserCasesPanel.class.getName());
private static final long serialVersionUID = 1L;
private final JDialog parentDialog;
private final CaseBrowser caseBrowserPanel;
private final MultiUserCasesBrowserPanel caseBrowserPanel;
/**
* Constructs a panel that allows a user to open cases created by automated
* ingest.
* Constructs a JPanel that allows a user to open a multi-user case.
*
* @param parentDialog The parent dialog of the panel, may be null. If
* provided, the dialog is hidden when this poanel's
* cancel button is pressed.
*/
MultiUserCasesPanel(JDialog parentDialog) {
OpenMultiUserCasePanel(JDialog parentDialog) {
this.parentDialog = parentDialog;
initComponents();
caseBrowserPanel = new CaseBrowser();
caseBrowserPanel = new MultiUserCasesBrowserPanel();
caseExplorerScrollPane.add(caseBrowserPanel);
caseExplorerScrollPane.setViewportView(caseBrowserPanel);
/*
* Listen for row selection changes and set button state for the current
* selection.
*/
caseBrowserPanel.addListSelectionListener((ListSelectionEvent e) -> {
setButtons();
});
}
/**
* Gets the list of cases known to the review mode cases manager and
* refreshes the cases table.
* Refreshes the child component that displays the multi-user cases known to
* the coordination service..
*/
void refresh() {
caseBrowserPanel.refresh();
void refreshDisplay() {
caseBrowserPanel.refreshCases();
}
/**
* Enables/disables the Open and Show Log buttons based on the case selected
* in the cases table.
*/
void setButtons() {
bnOpen.setEnabled(caseBrowserPanel.isRowSelected());
}
/**
* Open a case.
*
* @param caseMetadataFilePath The path to the case metadata file.
*/
private void openCase(String caseMetadataFilePath) {
if (caseMetadataFilePath != null) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
StartupWindowProvider.getInstance().close();
if (parentDialog != null) {
parentDialog.setVisible(false);
}
new Thread(() -> {
try {
Case.openAsCurrentCase(caseMetadataFilePath);
} catch (CaseActionException ex) {
if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS
MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage());
}
SwingUtilities.invokeLater(() -> {
//GUI changes done back on the EDT
StartupWindowProvider.getInstance().open();
});
} finally {
SwingUtilities.invokeLater(() -> {
//GUI changes done back on the EDT
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
}
}).start();
}
}
/**
* RowSorter which makes columns whose type is Date to be sorted first in
* Descending order then in Ascending order
* A RowSorter which causes columns whose type is Date to be sorted in
* either descending order or ascending order with descending order as the
* default.
*/
private static class RowSorter<M extends DefaultTableModel> extends TableRowSorter<M> {
@ -155,7 +100,6 @@ final class MultiUserCasesPanel extends JPanel{
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
bnOpen = new javax.swing.JButton();
bnOpenSingleUserCase = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
searchLabel = new javax.swing.JLabel();
@ -164,18 +108,7 @@ final class MultiUserCasesPanel extends JPanel{
setName("Completed Cases"); // NOI18N
setPreferredSize(new java.awt.Dimension(960, 485));
org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpen.text")); // NOI18N
bnOpen.setEnabled(false);
bnOpen.setMaximumSize(new java.awt.Dimension(80, 23));
bnOpen.setMinimumSize(new java.awt.Dimension(80, 23));
bnOpen.setPreferredSize(new java.awt.Dimension(80, 23));
bnOpen.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
bnOpenActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpenSingleUserCase.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.bnOpenSingleUserCase.text")); // NOI18N
bnOpenSingleUserCase.setMinimumSize(new java.awt.Dimension(156, 23));
bnOpenSingleUserCase.setPreferredSize(new java.awt.Dimension(156, 23));
bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() {
@ -184,7 +117,7 @@ final class MultiUserCasesPanel extends JPanel{
}
});
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.cancelButton.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.cancelButton.text")); // NOI18N
cancelButton.setMaximumSize(new java.awt.Dimension(80, 23));
cancelButton.setMinimumSize(new java.awt.Dimension(80, 23));
cancelButton.setPreferredSize(new java.awt.Dimension(80, 23));
@ -194,7 +127,7 @@ final class MultiUserCasesPanel extends JPanel{
}
});
org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.searchLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(searchLabel, org.openide.util.NbBundle.getMessage(OpenMultiUserCasePanel.class, "OpenMultiUserCasePanel.searchLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@ -206,17 +139,12 @@ final class MultiUserCasesPanel extends JPanel{
.addComponent(caseExplorerScrollPane)
.addGroup(layout.createSequentialGroup()
.addComponent(searchLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 350, Short.MAX_VALUE)
.addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(190, 190, 190)
.addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap())
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnOpen, cancelButton});
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
@ -225,7 +153,6 @@ final class MultiUserCasesPanel extends JPanel{
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
@ -233,14 +160,10 @@ final class MultiUserCasesPanel extends JPanel{
}// </editor-fold>//GEN-END:initComponents
/**
* Open button action
* Opens the standard open single user case window.
*
* @param evt -- The event that caused this to be called
* @param evt An ActionEvent, unused.
*/
private void bnOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenActionPerformed
openCase(caseBrowserPanel.getCasePath());
}//GEN-LAST:event_bnOpenActionPerformed
private void bnOpenSingleUserCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOpenSingleUserCaseActionPerformed
Lookup.getDefault().lookup(CaseOpenAction.class).openCaseSelectionWindow();
}//GEN-LAST:event_bnOpenSingleUserCaseActionPerformed
@ -252,7 +175,6 @@ final class MultiUserCasesPanel extends JPanel{
}//GEN-LAST:event_cancelButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton bnOpen;
private javax.swing.JButton bnOpenSingleUserCase;
private javax.swing.JButton cancelButton;
private javax.swing.JScrollPane caseExplorerScrollPane;

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -20,21 +20,40 @@ package org.sleuthkit.autopsy.coordinationservice;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
/**
* An object that converts case data for a case directory coordination service
* An object that converts data for a case directory lock coordination service
* node to and from byte arrays.
*/
public final class CaseNodeData {
private static final int CURRENT_VERSION = 0;
private static final int CURRENT_VERSION = 1;
/*
* Version 0 fields.
*/
private int version;
private boolean errorsOccurred;
/*
* Version 1 fields.
*/
private Path directory;
private long createDate;
private long lastAccessDate;
private String name;
private String displayName;
private short deletedItemFlags;
/**
* Gets the current version of the case directory coordination service node
* data.
* Gets the current version of the case directory lock coordination service
* node data.
*
* @return The version number.
*/
@ -42,9 +61,30 @@ public final class CaseNodeData {
return CaseNodeData.CURRENT_VERSION;
}
/**
* Uses a CaseMetadata object to construct an object that converts data for
* a case directory lock coordination service node to and from byte arrays.
*
* @param metadata The case meta data.
*
* @throws java.text.ParseException If there is an error parsing dates from
* string representations of dates in the
* meta data.
*/
public CaseNodeData(CaseMetadata metadata) throws ParseException {
this.version = CURRENT_VERSION;
this.errorsOccurred = false;
this.directory = Paths.get(metadata.getCaseDirectory());
this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate()).getTime();
this.lastAccessDate = new Date().getTime(); // Don't really know.
this.name = metadata.getCaseName();
this.displayName = metadata.getCaseDisplayName();
this.deletedItemFlags = 0;
}
/**
* Uses coordination service node data to construct an object that converts
* case data for a case directory coordination service node to and from byte
* data for a case directory lock coordination service node to and from byte
* arrays.
*
* @param nodeData The raw bytes received from the coordination service.
@ -53,34 +93,56 @@ public final class CaseNodeData {
* expected.
*/
public CaseNodeData(byte[] nodeData) throws InvalidDataException {
if(nodeData == null || nodeData.length == 0) {
this.version = CURRENT_VERSION;
this.errorsOccurred = false;
} else {
/*
* Get fields from node data.
*/
ByteBuffer buffer = ByteBuffer.wrap(nodeData);
try {
if (buffer.hasRemaining()) {
this.version = buffer.getInt();
if (nodeData == null || nodeData.length == 0) {
throw new InvalidDataException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array");
}
/*
* Flags bit format: 76543210
* 0-6 --> reserved for future use
* 7 --> errorsOccurred
*/
byte flags = buffer.get();
this.errorsOccurred = (flags < 0);
}
} catch (BufferUnderflowException ex) {
throw new InvalidDataException("Node data is incomplete", ex);
/*
* Get the fields from the node data.
*/
ByteBuffer buffer = ByteBuffer.wrap(nodeData);
try {
/*
* Get version 0 fields.
*/
this.version = buffer.getInt();
/*
* Flags bit format: 76543210 0-6 --> reserved for future use 7 -->
* errorsOccurred
*/
byte flags = buffer.get();
this.errorsOccurred = (flags < 0);
if (buffer.hasRemaining()) {
/*
* Get version 1 fields.
*/
this.directory = Paths.get(NodeDataUtils.getStringFromBuffer(buffer));
this.createDate = buffer.getLong();
this.lastAccessDate = buffer.getLong();
this.name = NodeDataUtils.getStringFromBuffer(buffer);
this.displayName = NodeDataUtils.getStringFromBuffer(buffer);
this.deletedItemFlags = buffer.getShort();
}
} catch (BufferUnderflowException ex) {
throw new InvalidDataException("Node data is incomplete", ex);
}
}
/**
* Gets whether or not any errors occurred during the processing of the job.
* Gets the node data version number of this node.
*
* @return The version number.
*/
public int getVersion() {
return this.version;
}
/**
* Gets whether or not any errors occurred during the processing of any auto
* ingest job for the case represented by this node data.
*
* @return True or false.
*/
@ -89,7 +151,8 @@ public final class CaseNodeData {
}
/**
* Sets whether or not any errors occurred during the processing of job.
* Sets whether or not any errors occurred during the processing of any auto
* ingest job for the case represented by this node data.
*
* @param errorsOccurred True or false.
*/
@ -98,12 +161,97 @@ public final class CaseNodeData {
}
/**
* Gets the node data version number.
* Gets the path of the case directory of the case represented by this node
* data.
*
* @return The version number.
* @return The case directory path.
*/
public int getVersion() {
return this.version;
public Path getDirectory() {
return this.directory;
}
/**
* Sets the path of the case directory of the case represented by this node
* data.
*
* @param caseDirectory The case directory path.
*/
public void setDirectory(Path caseDirectory) {
this.directory = caseDirectory;
}
/**
* Gets the date the case represented by this node data was created.
*
* @return The create date.
*/
public Date getCreateDate() {
return new Date(this.createDate);
}
/**
* Sets the date the case represented by this node data was created.
*
* @param createDate The create date.
*/
public void setCreateDate(Date createDate) {
this.createDate = createDate.getTime();
}
/**
* Gets the date the case represented by this node data last accessed.
*
* @return The last access date.
*/
public Date getLastAccessDate() {
return new Date(this.lastAccessDate);
}
/**
* Sets the date the case represented by this node data was last accessed.
*
* @param lastAccessDate The last access date.
*/
public void setLastAccessDate(Date lastAccessDate) {
this.lastAccessDate = lastAccessDate.getTime();
}
/**
* Gets the unique and immutable (user cannot change it) name of the case
* represented by this node data.
*
* @return The case name.
*/
public String getName() {
return this.name;
}
/**
* Sets the unique and immutable (user cannot change it) name of the case
* represented by this node data.
*
* @param name The case name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the display name of the case represented by this node data.
*
* @return The case display name.
*/
public String getDisplayName() {
return this.displayName;
}
/**
* Sets the display name of the case represented by this node data.
*
* @param displayName The case display name.
*/
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
/**
@ -113,16 +261,31 @@ public final class CaseNodeData {
* @return The node data as a byte array.
*/
public byte[] toArray() {
ByteBuffer buffer = ByteBuffer.allocate(5);
int bufferSize = Integer.BYTES; // version
bufferSize += 1; // errorsOccurred
bufferSize += this.directory.toString().getBytes().length; // directory
bufferSize += Long.BYTES; // createDate
bufferSize += Long.BYTES; // lastAccessDate
bufferSize += this.name.getBytes().length; // name
bufferSize += this.displayName.getBytes().length; // displayName
bufferSize += Short.BYTES; // deletedItemFlags
ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
buffer.putInt(this.version);
buffer.put((byte)(this.errorsOccurred ? 0x80 : 0));
buffer.put((byte) (this.errorsOccurred ? 0x80 : 0));
if (this.version >= 1) {
NodeDataUtils.putStringIntoBuffer(this.directory.toString(), buffer);
buffer.putLong(this.createDate);
buffer.putLong(this.lastAccessDate);
NodeDataUtils.putStringIntoBuffer(name, buffer);
NodeDataUtils.putStringIntoBuffer(displayName, buffer);
buffer.putShort(deletedItemFlags);
}
// Prepare the array
byte[] array = new byte[buffer.position()];
buffer.rewind();
buffer.get(array, 0, array.length);
return array;
}

View File

@ -0,0 +1,35 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.coordinationservice;
/**
* Exception thrown when invlaid coordniation service node data is encountered.
*/
public final class InvalidNodeDataException extends Exception {
private static final long serialVersionUID = 1L;
private InvalidNodeDataException(String message) {
super(message);
}
private InvalidNodeDataException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,64 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.coordinationservice;
import java.nio.ByteBuffer;
import javax.lang.model.type.TypeKind;
/**
* Utilities for reading and writing node data.
*/
public final class NodeDataUtils {
/**
* This method retrieves a string from a given buffer. Assumes the string
* was written using putStringIntoBuffer and first reads the the length of
* the string.
*
* @param buffer The buffer from which the string will be read.
*
* @return The string read from the buffer.
*/
public static String getStringFromBuffer(ByteBuffer buffer) {
String output = "";
int length = buffer.getInt();
if (length > 0) {
byte[] array = new byte[length];
buffer.get(array, 0, length);
output = new String(array);
}
return output;
}
/**
* This method puts a given string into a given buffer. The length of the
* string will be inserted prior to the string.
*
* @param stringValue The string to write to the buffer.
* @param buffer The buffer to which the string will be written.
*/
public static void putStringIntoBuffer(String stringValue, ByteBuffer buffer) {
buffer.putInt(stringValue.getBytes().length);
buffer.put(stringValue.getBytes());
}
private NodeDataUtils() {
}
}