mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-05 00:36:16 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 4877-IG-live-update
# Conflicts: # ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
This commit is contained in:
@@ -40,6 +40,24 @@
|
||||
<fileset dir="${thirdparty.dir}/Tesseract-OCR"/>
|
||||
</copy>
|
||||
|
||||
<!--Copy GStreamer to release-->
|
||||
<copy todir="${basedir}/release/gstreamer" >
|
||||
<fileset dir="${thirdparty.dir}/gstreamer"/>
|
||||
</copy>
|
||||
|
||||
<!-- The 'libgstlibav.dll' file is too big to store on GitHub, so we
|
||||
have it stored in a ZIP file. We'll extract it in place and remove
|
||||
the ZIP file afterward. -->
|
||||
<property name="gstreamer-base-path" value="${basedir}/release/gstreamer/1.0/" />
|
||||
|
||||
<unzip src="${gstreamer-base-path}/x86_64/lib/gstreamer-1.0/libgstlibav.zip"
|
||||
dest="${gstreamer-base-path}/x86_64/lib/gstreamer-1.0/"/>
|
||||
<delete file="${gstreamer-base-path}/x86_64/lib/gstreamer-1.0/libgstlibav.zip" />
|
||||
|
||||
<unzip src="${gstreamer-base-path}/x86/lib/gstreamer-1.0/libgstlibav.zip"
|
||||
dest="${gstreamer-base-path}/x86/lib/gstreamer-1.0/"/>
|
||||
<delete file="${gstreamer-base-path}/x86/lib/gstreamer-1.0/libgstlibav.zip" />
|
||||
|
||||
<!--Copy other jars-->
|
||||
<copy file="${thirdparty.dir}/rejistry/Rejistry-1.0-SNAPSHOT.jar" todir="${ext.dir}" />
|
||||
<copy file="${thirdparty.dir}/sevenzip/sevenzipjbinding.jar" todir="${ext.dir}" />
|
||||
|
||||
@@ -11,7 +11,7 @@ Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}
|
||||
Case.deleteCaseFailureMessageBox.title=Failed to Delete Case
|
||||
Case.exceptionMessage.cancelledByUser=Cancelled by user.
|
||||
Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.
|
||||
Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service.
|
||||
Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.
|
||||
Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window
|
||||
Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case.
|
||||
# {0} - exception message
|
||||
@@ -32,12 +32,19 @@ Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0
|
||||
Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}.
|
||||
# {0} - case display name
|
||||
Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled.
|
||||
Case.exceptionMessage.emptyCaseDir=Must specify a case directory path.
|
||||
Case.exceptionMessage.emptyCaseName=Must specify a case name.
|
||||
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details
|
||||
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.execExceptionWrapperMessage={0}
|
||||
Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.
|
||||
Case.exceptionMessage.metadataUpdateError=Failed to update case metadata
|
||||
@@ -50,23 +57,26 @@ Case.progressIndicatorTitle.creatingCase=Creating Case
|
||||
Case.progressIndicatorTitle.deletingCase=Deleting Case
|
||||
Case.progressIndicatorTitle.openingCase=Opening Case
|
||||
Case.progressMessage.cancelling=Cancelling...
|
||||
Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...
|
||||
Case.progressMessage.clearingTempDirectory=Clearing case temp directory...
|
||||
Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...
|
||||
Case.progressMessage.closingCaseDatabase=Closing case database...
|
||||
Case.progressMessage.closingCaseLevelServices=Closing case-level services...
|
||||
Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...
|
||||
Case.progressMessage.creatingCaseDatabase=Creating case database...
|
||||
Case.progressMessage.creatingCaseDirectory=Creating case directory...
|
||||
Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...
|
||||
Case.progressMessage.deletingCaseDatabase=Deleting case database...
|
||||
Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node...
|
||||
Case.progressMessage.deletingCaseDirectory=Deleting case directory...
|
||||
Case.progressMessage.deletingCoordinationServiceNodes=Deleting coordination service nodes...
|
||||
Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...
|
||||
Case.progressMessage.deletingTextIndex=Deleting text index...
|
||||
Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...
|
||||
Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...
|
||||
Case.progressMessage.openingCaseDatabase=Opening case database...
|
||||
Case.progressMessage.openingCaseLevelServices=Opening case-level services...
|
||||
Case.progressMessage.preparing=Preparing...
|
||||
Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>
|
||||
Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu...
|
||||
Case.progressMessage.savingCaseMetadata=Saving case metadata to file...
|
||||
Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...
|
||||
Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import java.awt.Frame;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -31,6 +32,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.text.ParseException;
|
||||
@@ -79,6 +81,8 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||
import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction;
|
||||
import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction;
|
||||
@@ -119,7 +123,6 @@ import org.sleuthkit.datamodel.Report;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
|
||||
import org.sleuthkit.autopsy.coreutils.StopWatch;
|
||||
|
||||
/**
|
||||
* An Autopsy case. Currently, only one case at a time may be open.
|
||||
@@ -139,6 +142,7 @@ public class Case {
|
||||
private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
|
||||
private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
|
||||
private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
|
||||
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
|
||||
private static final Logger logger = Logger.getLogger(Case.class.getName());
|
||||
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
|
||||
private static final Object caseActionSerializationLock = new Object();
|
||||
@@ -699,38 +703,28 @@ public class Case {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a case. This method cannot be used to delete the current case;
|
||||
* deleting the current case must be done by calling Case.deleteCurrentCase.
|
||||
* Deletes a case. The case to be deleted must not be the "current case."
|
||||
* Deleting the current case must be done by calling Case.deleteCurrentCase.
|
||||
*
|
||||
* @param metadata The metadata for the case to delete.
|
||||
* @param metadata The case metadata.
|
||||
*
|
||||
* @throws CaseActionException if there is a problem deleting the case. The
|
||||
* exception will have a user-friendly message
|
||||
* and may be a wrapper for a lower-level
|
||||
* exception.
|
||||
* @throws CaseActionException If there were one or more errors deleting the
|
||||
* case. The exception will have a user-friendly
|
||||
* message and may be a wrapper for a
|
||||
* lower-level exception.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressIndicatorTitle.deletingCase=Deleting Case",
|
||||
"Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
|
||||
"Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
|
||||
"Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service.",
|
||||
"Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case."
|
||||
"# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
|
||||
})
|
||||
public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
synchronized (caseActionSerializationLock) {
|
||||
if (null != currentCase) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
|
||||
}
|
||||
}
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to acquire caseActionSerializationLock (Java monitor in Case class) for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
|
||||
/*
|
||||
* Set up either a GUI progress indicator without a cancel button (can't
|
||||
* cancel deleting a case) or a logging progress indicator.
|
||||
*/
|
||||
ProgressIndicator progressIndicator;
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
|
||||
@@ -740,32 +734,17 @@ public class Case {
|
||||
progressIndicator.start(Bundle.Case_progressMessage_preparing());
|
||||
try {
|
||||
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
|
||||
deleteCase(metadata, progressIndicator);
|
||||
deleteSingleUserCase(metadata, progressIndicator);
|
||||
} else {
|
||||
/*
|
||||
* First, acquire an exclusive case directory lock. The case
|
||||
* cannot be deleted if another node has it open.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
try (CoordinationService.Lock dirLock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to acquire case directory coordination service lock for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
if (dirLock != null) {
|
||||
deleteCase(metadata, progressIndicator);
|
||||
} else {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
|
||||
}
|
||||
} catch (CoordinationServiceException ex) {
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to fail to acquire case directory coordination service lock for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_failedToDeleteCoordinationServiceNodes(), ex);
|
||||
}
|
||||
try {
|
||||
deleteCoordinationServiceNodes(metadata, progressIndicator);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
|
||||
deleteMultiUserCase(metadata, progressIndicator);
|
||||
} catch (InterruptedException ex) {
|
||||
/*
|
||||
* Note that task cancellation is not currently supported
|
||||
* for this code path, so this catch block is not expected
|
||||
* to be executed.
|
||||
*/
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -773,43 +752,6 @@ public class Case {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the coordination nodes for a multi-user case.
|
||||
*
|
||||
* @param metadata The metadata for the case to delete.
|
||||
* @param progressIndicator The progress indicator for the deletion
|
||||
* operation.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is a problem getting the
|
||||
* coordination service.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.deletingCoordinationServiceNodes=Deleting coordination service nodes..."
|
||||
})
|
||||
static void deleteCoordinationServiceNodes(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CoordinationServiceException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCoordinationServiceNodes());
|
||||
CoordinationService coordinationService;
|
||||
coordinationService = CoordinationService.getInstance();
|
||||
String resourcesLockNodePath = metadata.getCaseDirectory() + "_resources";
|
||||
try {
|
||||
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
/*
|
||||
* Log but do not notify the user.
|
||||
*/
|
||||
logger.log(Level.SEVERE, String.format("Failed to delete resources lock coordination service node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
|
||||
}
|
||||
String caseDirectoryLockNodePath = metadata.getCaseDirectory();
|
||||
try {
|
||||
coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
/*
|
||||
* Log but do not notify the user.
|
||||
*/
|
||||
logger.log(Level.SEVERE, String.format("Failed to delete case directory lock coordination service node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new or existing case as the current case.
|
||||
*
|
||||
@@ -987,105 +929,6 @@ public class Case {
|
||||
return imgPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Deletes the case directory of a deleted case and removes the case form
|
||||
* the Recent Cases menu.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws UserPreferencesException if there is a problem getting the case
|
||||
* databse connection info for a multi-user
|
||||
* case.
|
||||
* @throws ClassNotFoundException if there is a problem loading the JDBC
|
||||
* driver for PostgreSQL for a multi-user
|
||||
* case.
|
||||
* @throws SQLException If there is a problem
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.deletingTextIndex=Deleting text index...",
|
||||
"Case.progressMessage.deletingCaseDatabase=Deleting case database...",
|
||||
"Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
|
||||
"Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
|
||||
})
|
||||
private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
boolean errorsOccurred = false;
|
||||
if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
|
||||
/*
|
||||
* Delete the case database from the database server.
|
||||
*/
|
||||
stopWatch.start();
|
||||
try {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
|
||||
CaseDbConnectionInfo db;
|
||||
db = UserPreferences.getDatabaseConnectionInfo();
|
||||
Class.forName("org.postgresql.Driver"); //NON-NLS
|
||||
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
|
||||
Statement statement = connection.createStatement();) {
|
||||
String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
|
||||
statement.execute(deleteCommand);
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to delete case database for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
}
|
||||
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to delete case database %s for %s (%s) in %s", metadata.getCaseDatabaseName(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
|
||||
errorsOccurred = true;
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to fail delete case database for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete the text index.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
|
||||
for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
|
||||
try {
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
searchService.deleteTextIndex(metadata);
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to delete text index for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
} catch (KeywordSearchServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
|
||||
errorsOccurred = true;
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to fail to delete text index for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete the case directory.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
errorsOccurred = true;
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to fail to delete case directory for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
} else {
|
||||
stopWatch.stop();
|
||||
logger.log(Level.INFO, String.format("Used %d s to delete case directory for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||
}
|
||||
|
||||
/*
|
||||
* If running in a GUI, remove the case from the Recent Cases menu
|
||||
*/
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
|
||||
});
|
||||
}
|
||||
|
||||
if (errorsOccurred) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires an exclusive case resources lock.
|
||||
*
|
||||
@@ -1101,7 +944,8 @@ public class Case {
|
||||
})
|
||||
private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
|
||||
try {
|
||||
String resourcesNodeName = caseDir + "_resources";
|
||||
Path caseDirPath = Paths.get(caseDir);
|
||||
String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
|
||||
Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
||||
return lock;
|
||||
} catch (InterruptedException ex) {
|
||||
@@ -1777,11 +1621,10 @@ public class Case {
|
||||
}
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
|
||||
try {
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
|
||||
CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
|
||||
nodeData.setDisplayName(caseDetails.getCaseDisplayName());
|
||||
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException | IOException ex) {
|
||||
CaseNodeData.writeCaseNodeData(nodeData);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
}
|
||||
@@ -2162,10 +2005,8 @@ public class Case {
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
|
||||
try {
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
CaseNodeData nodeData = new CaseNodeData(metadata);
|
||||
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) {
|
||||
CaseNodeData.createCaseNodeData(metadata);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
}
|
||||
@@ -2175,6 +2016,8 @@ public class Case {
|
||||
* Updates the node data for the case directory lock coordination service
|
||||
* node.
|
||||
*
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem completing the
|
||||
* operation. The exception will have a
|
||||
* user-friendly message and may be a wrapper
|
||||
@@ -2187,15 +2030,12 @@ public class Case {
|
||||
private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE) {
|
||||
try {
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
|
||||
nodeData.setLastAccessDate(new Date());
|
||||
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException | IOException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
try {
|
||||
CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
|
||||
nodeData.setLastAccessDate(new Date());
|
||||
CaseNodeData.writeCaseNodeData(nodeData);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2330,7 +2170,6 @@ public class Case {
|
||||
"# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
|
||||
})
|
||||
private void openAppServiceCaseResources(ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
|
||||
/*
|
||||
* Each service gets its own independently cancellable/interruptible
|
||||
* task, running in a named thread managed by an executor service, with
|
||||
@@ -2339,6 +2178,7 @@ public class Case {
|
||||
* possible to ensure that each service task completes before the next
|
||||
* one starts by awaiting termination of the executor service.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
|
||||
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
|
||||
/*
|
||||
* Create a progress indicator for the task and start the task. If
|
||||
@@ -2688,6 +2528,363 @@ public class Case {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single-user case.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws CaseActionException If there were one or more errors deleting the
|
||||
* case. The exception will have a user-friendly
|
||||
* message and may be a wrapper for a
|
||||
* lower-level exception.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
|
||||
})
|
||||
private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
boolean errorsOccurred = false;
|
||||
try {
|
||||
deleteTextIndex(metadata, progressIndicator);
|
||||
} catch (KeywordSearchServiceException ex) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
deleteCaseDirectory(metadata, progressIndicator);
|
||||
} catch (CaseActionException ex) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
}
|
||||
|
||||
deleteFromRecentCases(metadata, progressIndicator);
|
||||
|
||||
if (errorsOccurred) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a multi-user case. This method does so after acquiring the case
|
||||
* directory coordination service lock and is intended to be used for
|
||||
* deleting simple multi-user cases without auto ingest input. Note that the
|
||||
* case directory coordination service node for the case is only deleted if
|
||||
* no errors occurred.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws CaseActionException If there were one or more errors deleting
|
||||
* the case. The exception will have a
|
||||
* user-friendly message and may be a wrapper
|
||||
* for a lower-level exception.
|
||||
* @throws InterruptedException If the thread this code is running in is
|
||||
* interrupted while blocked, i.e., if
|
||||
* cancellation of the operation is detected
|
||||
* during a wait.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
|
||||
"# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
|
||||
"Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
|
||||
"# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
|
||||
"Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
|
||||
"# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
|
||||
"Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
|
||||
"Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
|
||||
})
|
||||
private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
|
||||
CoordinationService coordinationService;
|
||||
try {
|
||||
coordinationService = CoordinationService.getInstance();
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
CaseNodeData caseNodeData;
|
||||
boolean errorsOccurred = false;
|
||||
try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
|
||||
if (dirLock == null) {
|
||||
logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); //NON-NLS
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
|
||||
}
|
||||
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
|
||||
try {
|
||||
caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
|
||||
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
|
||||
try {
|
||||
String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
|
||||
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
if (!isNoNodeException(ex)) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
}
|
||||
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
if (!errorsOccurred) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
|
||||
try {
|
||||
String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
|
||||
coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
|
||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
errorsOccurred = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorsOccurred) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This is a "beta" method and is subject to change or removal
|
||||
* without notice!
|
||||
*
|
||||
* Deletes a mulit-user case by attempting to delete the case database, the
|
||||
* text index, the case directory, and the case resources coordination
|
||||
* service node for a case, and removes the case from the recent cases menu
|
||||
* of the main application window. Callers of this method MUST acquire and
|
||||
* release the case directory lock for the case and are responsible for
|
||||
* deleting the corresponding coordination service nodes, if desired.
|
||||
*
|
||||
* @param caseNodeData The coordination service node data for the case.
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param logger A logger.
|
||||
*
|
||||
* @return True if one or more errors occurred (see log for details), false
|
||||
* otherwise.
|
||||
*
|
||||
* @throws InterruptedException If the thread this code is running in is
|
||||
* interrupted while blocked, i.e., if
|
||||
* cancellation of the operation is detected
|
||||
* during a wait.
|
||||
*/
|
||||
@Beta
|
||||
public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
|
||||
boolean errorsOccurred = false;
|
||||
try {
|
||||
deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
|
||||
deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
|
||||
deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
|
||||
deleteFromRecentCases(metadata, progressIndicator);
|
||||
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
} catch (KeywordSearchServiceException ex) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
} catch (CaseActionException ex) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
}
|
||||
return errorsOccurred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete the case database for a multi-user case.
|
||||
*
|
||||
* @param caseNodeData The coordination service node data for the case.
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param logger A logger.
|
||||
*
|
||||
* @throws UserPreferencesException if there is an error getting the
|
||||
* database server connection info.
|
||||
* @throws ClassNotFoundException if there is an error gettting the
|
||||
* required JDBC driver.
|
||||
* @throws SQLException if there is an error executing the SQL
|
||||
* to drop the database from the database
|
||||
* server.
|
||||
* @throws InterruptedException If interrupted while blocked waiting for
|
||||
* coordination service data to be written
|
||||
* to the coordination service node
|
||||
* database.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.deletingCaseDatabase=Deleting case database..."
|
||||
})
|
||||
private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
|
||||
if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
|
||||
logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
|
||||
CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
|
||||
String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
|
||||
Class.forName("org.postgresql.Driver"); //NON-NLS
|
||||
try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
|
||||
String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
|
||||
try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
|
||||
if (queryResult.next()) {
|
||||
String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
|
||||
statement.execute(deleteCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete the text index for a multi-user case.
|
||||
*
|
||||
* @param caseNodeData The coordination service node data for the case.
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param logger A logger.
|
||||
*
|
||||
* @throws KeywordSearchServiceException If there is an error deleting the
|
||||
* text index.
|
||||
* @throws InterruptedException If interrupted while blocked
|
||||
* waiting for coordination service
|
||||
* data to be written to the
|
||||
* coordination service node database.
|
||||
*/
|
||||
private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
|
||||
if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) {
|
||||
logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
|
||||
deleteTextIndex(metadata, progressIndicator);
|
||||
setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete the text index for a case.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws KeywordSearchServiceException If there is an error deleting the
|
||||
* text index.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.deletingTextIndex=Deleting text index..."
|
||||
})
|
||||
private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
|
||||
for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
|
||||
searchService.deleteTextIndex(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete the case directory for a multi-user case.
|
||||
*
|
||||
* @param caseNodeData The coordination service node data for the case.
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param logger A logger.
|
||||
*
|
||||
* @throws CaseActionException if there is an error deleting the case
|
||||
* directory.
|
||||
* @throws InterruptedException If interrupted while blocked waiting for
|
||||
* coordination service data to be written to
|
||||
* the coordination service node database.
|
||||
*/
|
||||
private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
|
||||
if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
|
||||
logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
|
||||
deleteCaseDirectory(metadata, progressIndicator);
|
||||
setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete the case directory for a case.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws CaseActionException If there is an error deleting the case
|
||||
* directory.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
|
||||
})
|
||||
private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
|
||||
if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
|
||||
throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to remove a case from the recent cases menu if the main
|
||||
* application window is present.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
* @param progressIndicator A progress indicator.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
|
||||
})
|
||||
private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines a coordination service exception to try to determine if it is a
|
||||
* "no node" exception, i.e., an operation was attempted on a node that does
|
||||
* not exist.
|
||||
*
|
||||
* @param ex A coordination service exception.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
private static boolean isNoNodeException(CoordinationServiceException ex) {
|
||||
boolean isNodeNodeEx = false;
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause != null) {
|
||||
String causeMessage = cause.getMessage();
|
||||
isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
|
||||
}
|
||||
return isNodeNodeEx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a deleted item flag in the coordination service node data for a
|
||||
* multi-user case.
|
||||
*
|
||||
* @param caseNodeData The coordination service node data for the case.
|
||||
* @param flag The flag to set.
|
||||
*
|
||||
* @throws InterruptedException If interrupted while blocked waiting for
|
||||
* coordination service data to be written to
|
||||
* the coordination service node database.
|
||||
*/
|
||||
private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
|
||||
try {
|
||||
caseNodeData.setDeletedFlag(flag);
|
||||
CaseNodeData.writeCaseNodeData(caseNodeData);
|
||||
} catch (CaseNodeDataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A case operation Cancel button listener for use with a
|
||||
* ModalDialogProgressIndicator when running with a GUI.
|
||||
|
||||
@@ -95,13 +95,13 @@ 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
|
||||
|
||||
|
||||
/*
|
||||
* Fields from schema version 5
|
||||
*/
|
||||
private static final String SCHEMA_VERSION_FIVE = "5.0";
|
||||
private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS
|
||||
|
||||
|
||||
/*
|
||||
* Unread fields, regenerated on save.
|
||||
*/
|
||||
@@ -138,16 +138,16 @@ public final class CaseMetadata {
|
||||
public static DateFormat getDateFormat() {
|
||||
return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* called.
|
||||
*
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
*/
|
||||
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
|
||||
this(caseType, caseDirectory, caseName, caseDetails, null);
|
||||
@@ -158,11 +158,11 @@ public final class CaseMetadata {
|
||||
* persisted to the case metadata file until writeFile or a setX method is
|
||||
* called.
|
||||
*
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param originalMetadata The metadata object from the original case
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param originalMetadata The metadata object from the original case
|
||||
*/
|
||||
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) {
|
||||
metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
|
||||
@@ -190,25 +190,25 @@ public final class CaseMetadata {
|
||||
this.metadataFilePath = metadataFilePath;
|
||||
readFromFile();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Locate the case meta data file in the supplied directory. If the file does
|
||||
* not exist, null is returned.
|
||||
*
|
||||
* @param directoryPath Directory path to search
|
||||
* @return case meta data file path or null
|
||||
* Locate the case meta data file in the supplied directory. If the file
|
||||
* does not exist, null is returned.
|
||||
*
|
||||
* @param directoryPath Directory path to search.
|
||||
*
|
||||
* @return Case metadata file path or null.
|
||||
*/
|
||||
public static Path getCaseMetadataFile(Path directoryPath) {
|
||||
final File[] caseFiles = directoryPath.toFile().listFiles();
|
||||
if(caseFiles != null) {
|
||||
for (File file : caseFiles) {
|
||||
public static Path getCaseMetadataFilePath(Path directoryPath) {
|
||||
final File[] files = directoryPath.toFile().listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
final String fileName = file.getName().toLowerCase();
|
||||
if (fileName.endsWith(CaseMetadata.getFileExtension())) {
|
||||
if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
|
||||
return file.toPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ public final class CaseMetadata {
|
||||
* Create the children of the case element.
|
||||
*/
|
||||
createCaseElements(doc, caseElement, this);
|
||||
|
||||
|
||||
/*
|
||||
* Add original case element
|
||||
*/
|
||||
@@ -472,15 +472,15 @@ public final class CaseMetadata {
|
||||
originalCaseElement.appendChild(originalCaseDetailsElement);
|
||||
createCaseElements(doc, originalCaseDetailsElement, originalMetadata);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write the case element children for the given metadata object
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param caseElement The case element parent
|
||||
* @param metadataToWrite The CaseMetadata object to read from
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param caseElement The case element parent
|
||||
* @param metadataToWrite The CaseMetadata object to read from
|
||||
*/
|
||||
private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) {
|
||||
CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails;
|
||||
@@ -572,9 +572,8 @@ public final class CaseMetadata {
|
||||
examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
|
||||
caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
|
||||
}
|
||||
|
||||
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail,
|
||||
caseNotes);
|
||||
|
||||
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes);
|
||||
this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
|
||||
if (null == this.caseType) {
|
||||
throw new CaseMetadataException("Case metadata file corrupted");
|
||||
|
||||
@@ -22,25 +22,33 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* An object that converts data for a case directory lock coordination service
|
||||
* node to and from byte arrays.
|
||||
* Case data stored in a case directory coordination service node.
|
||||
*/
|
||||
public final class CaseNodeData {
|
||||
|
||||
private static final int CURRENT_VERSION = 1;
|
||||
private static final int MAJOR_VERSION = 2;
|
||||
private static final int MINOR_VERSION = 0;
|
||||
private static final Logger logger = Logger.getLogger(CaseNodeData.class.getName());
|
||||
|
||||
/*
|
||||
* Version 0 fields.
|
||||
* Version 0 fields. Note that version 0 node data was only written to the
|
||||
* coordination service node if an auto ingest job error occurred.
|
||||
*/
|
||||
private final int version;
|
||||
private int version;
|
||||
private boolean errorsOccurred;
|
||||
|
||||
/*
|
||||
@@ -53,28 +61,191 @@ public final class CaseNodeData {
|
||||
private String displayName;
|
||||
private short deletedItemFlags;
|
||||
|
||||
/**
|
||||
* Gets the current version of the case directory lock coordination service
|
||||
* node data.
|
||||
*
|
||||
* @return The version number.
|
||||
/*
|
||||
* Version 2 fields.
|
||||
*/
|
||||
public static int getCurrentVersion() {
|
||||
return CaseNodeData.CURRENT_VERSION;
|
||||
private int minorVersion;
|
||||
|
||||
/**
|
||||
* Creates case node data from the metadata for a case and writes it to the
|
||||
* appropriate case directory coordination service node, which must already
|
||||
* exist.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
*
|
||||
* @return The case node data that was written to the coordination service
|
||||
* node.
|
||||
*
|
||||
* @throws CaseNodeDataException If there is an error creating or writing
|
||||
* the case node data.
|
||||
* @throws InterruptedException If the current thread is interrupted while
|
||||
* waiting for the coordination service.
|
||||
*/
|
||||
public static CaseNodeData createCaseNodeData(final CaseMetadata metadata) throws CaseNodeDataException, InterruptedException {
|
||||
try {
|
||||
final CaseNodeData nodeData = new CaseNodeData(metadata);
|
||||
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeData.getDirectory().toString(), nodeData.toArray());
|
||||
return nodeData;
|
||||
|
||||
} catch (ParseException | IOException | CoordinationServiceException ex) {
|
||||
throw new CaseNodeDataException(String.format("Error creating case node data for coordination service node with path %s", metadata.getCaseDirectory().toUpperCase()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a CaseMetadata object to construct an object that converts data for
|
||||
* a case directory lock coordination service node to and from byte arrays.
|
||||
* Reads case data from a case directory coordination service node. If the
|
||||
* data is missing, corrupted, or from an older version of the software, an
|
||||
* attempt is made to remedy the situation using the case metadata.
|
||||
*
|
||||
* @param nodePath The case directory coordination service node path.
|
||||
*
|
||||
* @return The case node data.
|
||||
*
|
||||
* @throws CaseNodeDataException If there is an error reading or writing the
|
||||
* case node data.
|
||||
* @throws InterruptedException If the current thread is interrupted while
|
||||
* waiting for the coordination service.
|
||||
*/
|
||||
public static CaseNodeData readCaseNodeData(String nodePath) throws CaseNodeDataException, InterruptedException {
|
||||
try {
|
||||
CaseNodeData nodeData;
|
||||
final byte[] nodeBytes = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, nodePath);
|
||||
if (nodeBytes != null && nodeBytes.length > 0) {
|
||||
try {
|
||||
nodeData = new CaseNodeData(nodeBytes);
|
||||
} catch (IOException ex) {
|
||||
/*
|
||||
* The existing case node data is corrupted.
|
||||
*/
|
||||
logger.log(Level.WARNING, String.format("Error reading node data for coordination service node with path %s, will attempt to replace it", nodePath.toUpperCase()), ex); //NON-NLS
|
||||
final CaseMetadata metadata = getCaseMetadata(nodePath);
|
||||
nodeData = createCaseNodeData(metadata);
|
||||
logger.log(Level.INFO, String.format("Replaced corrupt node data for coordination service node with path %s", nodePath.toUpperCase())); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* The case node data is missing. Version 0 node data was only
|
||||
* written to the coordination service node if an auto ingest
|
||||
* job error occurred.
|
||||
*/
|
||||
logger.log(Level.INFO, String.format("Missing node data for coordination service node with path %s, will attempt to create it", nodePath.toUpperCase())); //NON-NLS
|
||||
final CaseMetadata metadata = getCaseMetadata(nodePath);
|
||||
nodeData = createCaseNodeData(metadata);
|
||||
logger.log(Level.INFO, String.format("Created node data for coordination service node with path %s", nodePath.toUpperCase())); //NON-NLS
|
||||
}
|
||||
if (nodeData.getVersion() < CaseNodeData.MAJOR_VERSION) {
|
||||
nodeData = upgradeCaseNodeData(nodePath, nodeData);
|
||||
}
|
||||
return nodeData;
|
||||
|
||||
} catch (CaseNodeDataException | CaseMetadataException | ParseException | IOException | CoordinationServiceException ex) {
|
||||
throw new CaseNodeDataException(String.format("Error reading/writing node data coordination service node with path %s", nodePath.toUpperCase()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes case data to a case directory coordination service node. Obtain
|
||||
* the case data to be updated and written by calling createCaseNodeData()
|
||||
* or readCaseNodeData().
|
||||
*
|
||||
* @param nodeData The case node data.
|
||||
*
|
||||
* @throws CaseNodeDataException If there is an error writing the case node
|
||||
* data.
|
||||
* @throws InterruptedException If the current thread is interrupted while
|
||||
* waiting for the coordination service.
|
||||
*/
|
||||
public static void writeCaseNodeData(CaseNodeData nodeData) throws CaseNodeDataException, InterruptedException {
|
||||
try {
|
||||
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeData.getDirectory().toString(), nodeData.toArray());
|
||||
|
||||
} catch (IOException | CoordinationServiceException ex) {
|
||||
throw new CaseNodeDataException(String.format("Error writing node data coordination service node with path %s", nodeData.getDirectory().toString().toUpperCase()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades older versions of node data to the current version and writes
|
||||
* the data back to the case directory coordination service node.
|
||||
*
|
||||
* @param nodePath The case directory coordination service node path.
|
||||
* @param oldNodeData The outdated node data.
|
||||
*
|
||||
* @return The updated node data.
|
||||
*
|
||||
* @throws CaseNodeDataException If the case meta data file or case
|
||||
* directory do not exist.
|
||||
* @throws CaseMetadataException If the case metadata cannot be read.
|
||||
*/
|
||||
private static CaseNodeData upgradeCaseNodeData(String nodePath, CaseNodeData oldNodeData) throws CaseNodeDataException, CaseMetadataException, ParseException, IOException, CoordinationServiceException, InterruptedException {
|
||||
CaseNodeData nodeData;
|
||||
switch (oldNodeData.getVersion()) {
|
||||
case 0:
|
||||
/*
|
||||
* Version 0 node data consisted of only the version number and
|
||||
* the errors occurred flag and was only written when an auto
|
||||
* ingest job error occurred. To upgrade from version 0, the
|
||||
* version 1 fields need to be set from the case metadata and
|
||||
* the errors occurred flag needs to be carried forward. Note
|
||||
* that the last accessed date gets advanced to now, since it is
|
||||
* otherwise unknown.
|
||||
*/
|
||||
final CaseMetadata metadata = getCaseMetadata(nodePath);
|
||||
nodeData = new CaseNodeData(metadata);
|
||||
nodeData.setErrorsOccurred(oldNodeData.getErrorsOccurred());
|
||||
break;
|
||||
case 1:
|
||||
/*
|
||||
* Version 1 node data did not have a minor version number
|
||||
* field.
|
||||
*/
|
||||
oldNodeData.setMinorVersion(MINOR_VERSION);
|
||||
nodeData = oldNodeData;
|
||||
break;
|
||||
default:
|
||||
nodeData = oldNodeData;
|
||||
break;
|
||||
}
|
||||
writeCaseNodeData(nodeData);
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the metadata for a case.
|
||||
*
|
||||
* @param nodePath The case directory coordination service node path for the
|
||||
* case.
|
||||
*
|
||||
* @return The case metadata.
|
||||
*
|
||||
* @throws CaseNodeDataException If the case metadata file or the case
|
||||
* directory does not exist.
|
||||
* @throws CaseMetadataException If the case metadata cannot be read.
|
||||
*/
|
||||
private static CaseMetadata getCaseMetadata(String nodePath) throws CaseNodeDataException, CaseMetadataException {
|
||||
final Path caseDirectoryPath = Paths.get(nodePath);
|
||||
final File caseDirectory = caseDirectoryPath.toFile();
|
||||
if (!caseDirectory.exists()) {
|
||||
throw new CaseNodeDataException("Case directory does not exist"); // NON-NLS
|
||||
}
|
||||
final Path metadataFilePath = CaseMetadata.getCaseMetadataFilePath(caseDirectoryPath);
|
||||
if (metadataFilePath == null) {
|
||||
throw new CaseNodeDataException("Case meta data file does not exist"); // NON-NLS
|
||||
}
|
||||
return new CaseMetadata(metadataFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses case metadata to construct the case data to store in a case
|
||||
* directory coordination service node.
|
||||
*
|
||||
* @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.
|
||||
* @throws 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;
|
||||
private CaseNodeData(CaseMetadata metadata) throws ParseException {
|
||||
this.version = MAJOR_VERSION;
|
||||
this.errorsOccurred = false;
|
||||
this.directory = Paths.get(metadata.getCaseDirectory());
|
||||
this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate());
|
||||
@@ -82,51 +253,64 @@ public final class CaseNodeData {
|
||||
this.name = metadata.getCaseName();
|
||||
this.displayName = metadata.getCaseDisplayName();
|
||||
this.deletedItemFlags = 0;
|
||||
this.minorVersion = MINOR_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses coordination service node data to construct an object that converts
|
||||
* data for a case directory lock coordination service node to and from byte
|
||||
* arrays.
|
||||
* Uses the raw bytes from a case directory coordination service node to
|
||||
* construct a case node data object.
|
||||
*
|
||||
* @param nodeData The raw bytes received from the coordination service.
|
||||
*
|
||||
* @throws IOException If there is an error reading the node data.
|
||||
*/
|
||||
public CaseNodeData(byte[] nodeData) throws IOException {
|
||||
private CaseNodeData(byte[] nodeData) throws IOException {
|
||||
if (nodeData == null || nodeData.length == 0) {
|
||||
throw new IOException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array");
|
||||
}
|
||||
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(nodeData));
|
||||
this.version = inputStream.readInt();
|
||||
if (this.version > 0) {
|
||||
this.errorsOccurred = inputStream.readBoolean();
|
||||
} else {
|
||||
short legacyErrorsOccurred = inputStream.readByte();
|
||||
this.errorsOccurred = (legacyErrorsOccurred < 0);
|
||||
}
|
||||
if (this.version > 0) {
|
||||
this.directory = Paths.get(inputStream.readUTF());
|
||||
this.createDate = new Date(inputStream.readLong());
|
||||
this.lastAccessDate = new Date(inputStream.readLong());
|
||||
this.name = inputStream.readUTF();
|
||||
this.displayName = inputStream.readUTF();
|
||||
this.deletedItemFlags = inputStream.readShort();
|
||||
try (ByteArrayInputStream byteStream = new ByteArrayInputStream(nodeData); DataInputStream inputStream = new DataInputStream(byteStream)) {
|
||||
this.version = inputStream.readInt();
|
||||
if (this.version == 1) {
|
||||
this.errorsOccurred = inputStream.readBoolean();
|
||||
} else {
|
||||
byte errorsOccurredByte = inputStream.readByte();
|
||||
this.errorsOccurred = (errorsOccurredByte < 0);
|
||||
}
|
||||
if (this.version > 0) {
|
||||
this.directory = Paths.get(inputStream.readUTF());
|
||||
this.createDate = new Date(inputStream.readLong());
|
||||
this.lastAccessDate = new Date(inputStream.readLong());
|
||||
this.name = inputStream.readUTF();
|
||||
this.displayName = inputStream.readUTF();
|
||||
this.deletedItemFlags = inputStream.readShort();
|
||||
}
|
||||
if (this.version > 1) {
|
||||
this.minorVersion = inputStream.readInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node data version number of this node.
|
||||
* Gets the version number of this node data.
|
||||
*
|
||||
* @return The version number.
|
||||
*/
|
||||
public int getVersion() {
|
||||
private int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minor version number of this node data.
|
||||
*
|
||||
* @param minorVersion The version number.
|
||||
*/
|
||||
private void setMinorVersion(int minorVersion) {
|
||||
this.minorVersion = minorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether or not any errors occurred during the processing of any auto
|
||||
* ingest job for the case represented by this node data.
|
||||
* ingest job for the case.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
@@ -136,7 +320,7 @@ public final class CaseNodeData {
|
||||
|
||||
/**
|
||||
* Sets whether or not any errors occurred during the processing of any auto
|
||||
* ingest job for the case represented by this node data.
|
||||
* ingest job for the case.
|
||||
*
|
||||
* @param errorsOccurred True or false.
|
||||
*/
|
||||
@@ -145,8 +329,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the case directory of the case represented by this node
|
||||
* data.
|
||||
* Gets the path of the case directory.
|
||||
*
|
||||
* @return The case directory path.
|
||||
*/
|
||||
@@ -155,17 +338,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Gets the date the case was created.
|
||||
*
|
||||
* @return The create date.
|
||||
*/
|
||||
@@ -174,16 +347,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date the case represented by this node data was created.
|
||||
*
|
||||
* @param createDate The create date.
|
||||
*/
|
||||
public void setCreateDate(Date createDate) {
|
||||
this.createDate = new Date(createDate.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date the case represented by this node data last accessed.
|
||||
* Gets the date the case was last accessed.
|
||||
*
|
||||
* @return The last access date.
|
||||
*/
|
||||
@@ -192,7 +356,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date the case represented by this node data was last accessed.
|
||||
* Sets the date the case was last accessed.
|
||||
*
|
||||
* @param lastAccessDate The last access date.
|
||||
*/
|
||||
@@ -201,8 +365,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique and immutable (user cannot change it) name of the case
|
||||
* represented by this node data.
|
||||
* Gets the unique and immutable name of the case.
|
||||
*
|
||||
* @return The case name.
|
||||
*/
|
||||
@@ -211,17 +374,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Gets the display name of the case.
|
||||
*
|
||||
* @return The case display name.
|
||||
*/
|
||||
@@ -230,7 +383,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name of the case represented by this node data.
|
||||
* Sets the display name of the case.
|
||||
*
|
||||
* @param displayName The case display name.
|
||||
*/
|
||||
@@ -238,40 +391,113 @@ public final class CaseNodeData {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given deleted item flag is set for the case.
|
||||
*
|
||||
* @param flag The flag to check.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public boolean isDeletedFlagSet(DeletedFlags flag) {
|
||||
return (this.deletedItemFlags & flag.getValue()) == flag.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a given deleted item flag.
|
||||
*
|
||||
* @param flag The flag to set.
|
||||
*/
|
||||
public void setDeletedFlag(DeletedFlags flag) {
|
||||
this.deletedItemFlags |= flag.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node data as a byte array that can be sent to the coordination
|
||||
* service.
|
||||
*
|
||||
* @return The node data as a byte array.
|
||||
*
|
||||
* @throws IOException If there is an error writing the node data.
|
||||
* @throws IOException If there is an error writing the node data to the
|
||||
* array.
|
||||
*/
|
||||
public byte[] toArray() throws IOException {
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
DataOutputStream outputStream = new DataOutputStream(byteStream);
|
||||
outputStream.writeInt(this.version);
|
||||
outputStream.writeBoolean(this.errorsOccurred);
|
||||
outputStream.writeUTF(this.directory.toString());
|
||||
outputStream.writeLong(this.createDate.getTime());
|
||||
outputStream.writeLong(this.lastAccessDate.getTime());
|
||||
outputStream.writeUTF(this.name);
|
||||
outputStream.writeUTF(this.displayName);
|
||||
outputStream.writeShort(this.deletedItemFlags);
|
||||
outputStream.flush();
|
||||
byteStream.flush();
|
||||
return byteStream.toByteArray();
|
||||
private byte[] toArray() throws IOException {
|
||||
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(byteStream)) {
|
||||
outputStream.writeInt(this.version);
|
||||
outputStream.writeByte((byte) (this.errorsOccurred ? 0x80 : 0));
|
||||
outputStream.writeUTF(this.directory.toString());
|
||||
outputStream.writeLong(this.createDate.getTime());
|
||||
outputStream.writeLong(this.lastAccessDate.getTime());
|
||||
outputStream.writeUTF(this.name);
|
||||
outputStream.writeUTF(this.displayName);
|
||||
outputStream.writeShort(this.deletedItemFlags);
|
||||
outputStream.writeInt(this.minorVersion);
|
||||
outputStream.flush();
|
||||
byteStream.flush();
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
public final static class InvalidDataException extends Exception {
|
||||
/**
|
||||
* Flags for the various components of a case that can be deleted.
|
||||
*/
|
||||
public enum DeletedFlags {
|
||||
|
||||
TEXT_INDEX(1),
|
||||
CASE_DB(2),
|
||||
CASE_DIR(4),
|
||||
DATA_SOURCES(8),
|
||||
MANIFEST_FILE_NODES(16);
|
||||
|
||||
private final short value;
|
||||
|
||||
/**
|
||||
* Constructs a flag for a case component that can be deleted.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
private DeletedFlags(int value) {
|
||||
this.value = (short) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the flag.
|
||||
*
|
||||
* @return The value as a short.
|
||||
*/
|
||||
private short getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when there is an error reading or writing case node
|
||||
* data.
|
||||
*/
|
||||
public static final class CaseNodeDataException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private InvalidDataException(String message) {
|
||||
/**
|
||||
* Constructs an exception to throw when there is an error reading or
|
||||
* writing case node data.
|
||||
*
|
||||
* @param message The exception message.
|
||||
*/
|
||||
private CaseNodeDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
private InvalidDataException(String message, Throwable cause) {
|
||||
/**
|
||||
* Constructs an exception to throw when there is an error reading or
|
||||
* writing case node data.
|
||||
*
|
||||
* @param message The exception message.
|
||||
* @param cause The cause of the exception.
|
||||
*/
|
||||
private CaseNodeDataException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.casemodule.multiusercases;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseAutoIngestLogNodePath;
|
||||
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseNameNodePath;
|
||||
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseResourcesNodePath;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* Collects the multi-user case node data stored in the case directory
|
||||
* coordination service nodes.
|
||||
*/
|
||||
final public class CaseNodeDataCollector {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CaseNodeDataCollector.class.getName());
|
||||
|
||||
/**
|
||||
* Collects the multi-user case node data stored in the case directory
|
||||
* coordination service nodes.
|
||||
*
|
||||
* @return The node data for the multi-user cases known to the coordination
|
||||
* service.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error interacting
|
||||
* with the coordination service.
|
||||
* @throws InterruptedException If the current thread is interrupted
|
||||
* while waiting for the coordination
|
||||
* service.
|
||||
*/
|
||||
public static List<CaseNodeData> getNodeData() throws CoordinationServiceException, InterruptedException {
|
||||
final List<CaseNodeData> nodeDataList = new ArrayList<>();
|
||||
final CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
final List<String> nodePaths = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
|
||||
for (String nodePath : nodePaths) {
|
||||
/*
|
||||
* Skip the case name, case resources, and case auto ingest log
|
||||
* coordination service nodes. They are not used to store case data.
|
||||
*/
|
||||
if (isCaseNameNodePath(nodePath) || isCaseResourcesNodePath(nodePath) || isCaseAutoIngestLogNodePath(nodePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the case node data from the case directory coordination service node.
|
||||
*/
|
||||
try {
|
||||
final CaseNodeData nodeData = CaseNodeData.readCaseNodeData(nodePath);
|
||||
nodeDataList.add(nodeData);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error reading case node data from %s", nodePath), ex);
|
||||
}
|
||||
|
||||
}
|
||||
return nodeDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private CaseNodeDataCollector() {
|
||||
}
|
||||
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.casemodule.multiusercases;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for using the coordination service for multi-user cases.
|
||||
*/
|
||||
public final class CoordinationServiceUtils {
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Gets the path of the case resources coordination service node for a case.
|
||||
* This coordiantion service node is used for case resource locking.
|
||||
*
|
||||
* @param caseDirectoryPath The case directory path.
|
||||
*
|
||||
* @return The case resources coordination service node path.
|
||||
*/
|
||||
public static String getCaseResourcesNodePath(Path caseDirectoryPath) {
|
||||
return caseDirectoryPath + RESOURCES_LOCK_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the case auto ingest log coordination service node for a
|
||||
* case. This coordination service node is used for serializing case auto
|
||||
* ingest log writes.
|
||||
*
|
||||
* @param caseDirectoryPath The case directory path.
|
||||
*
|
||||
* @return The case auto ingest log coordination service node path.
|
||||
*/
|
||||
public static String getCaseAutoIngestLogNodePath(Path caseDirectoryPath) {
|
||||
return Paths.get(caseDirectoryPath.toString(), CASE_AUTO_INGEST_LOG_NAME).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the case directory coordination service node for a case.
|
||||
* This coordination service node is used for locking the case directory and
|
||||
* for storing data about the case.
|
||||
*
|
||||
* @param caseDirectoryPath The case directory path.
|
||||
*
|
||||
* @return The case directory coordination service node path.
|
||||
*/
|
||||
public static String getCaseDirectoryNodePath(Path caseDirectoryPath) {
|
||||
return caseDirectoryPath.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the case name coordination service node for a case. This
|
||||
* coordination service node is used to lock the case name so that only one
|
||||
* node at a time can create a case with a particular name.
|
||||
*
|
||||
* @param caseDirectoryPath The case directory path.
|
||||
*
|
||||
* @return The case name coordination service node path.
|
||||
*/
|
||||
public static String getCaseNameNodePath(Path caseDirectoryPath) {
|
||||
String caseName = caseDirectoryPath.getFileName().toString();
|
||||
if (TimeStampUtils.endsWithTimeStamp(caseName)) {
|
||||
caseName = TimeStampUtils.removeTimeStamp(caseName);
|
||||
if (caseName.endsWith("_")) {
|
||||
caseName = caseName.substring(0, caseName.length() - 1);
|
||||
}
|
||||
}
|
||||
return caseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a coordination service node path is a case auto
|
||||
* ingest node path.
|
||||
*
|
||||
* @param nodePath The node path.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean isCaseAutoIngestLogNodePath(String nodePath) {
|
||||
return Paths.get(nodePath).getFileName().toString().equals(CASE_AUTO_INGEST_LOG_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a coordination service node path is a case
|
||||
* resources node path.
|
||||
*
|
||||
* @param nodePath The node path.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean isCaseResourcesNodePath(String nodePath) {
|
||||
return Paths.get(nodePath).getFileName().toString().endsWith(RESOURCES_LOCK_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a coordination service node path is a case name
|
||||
* node path.
|
||||
*
|
||||
* @param nodePath The node path.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean isCaseNameNodePath(String nodePath) {
|
||||
return !(nodePath.contains("\\") || nodePath.contains("//"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents instantiation of this uitlity class.
|
||||
*/
|
||||
private CoordinationServiceUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* 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.casemodule.multiusercases;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* Queries the coordination service to collect the multi-user case node data
|
||||
* stored in the case directory lock ZooKeeper nodes.
|
||||
*/
|
||||
final public class MultiUserCaseNodeDataCollector {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MultiUserCaseNodeDataCollector.class.getName());
|
||||
private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS
|
||||
private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS
|
||||
|
||||
/**
|
||||
* Queries the coordination service to collect the multi-user case node data
|
||||
* stored in the case directory lock ZooKeeper nodes.
|
||||
*
|
||||
* @return A list of CaseNodedata objects that convert data for a case
|
||||
* directory lock coordination service node to and from byte arrays.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error
|
||||
*/
|
||||
public static List<CaseNodeData> getNodeData() throws CoordinationService.CoordinationServiceException {
|
||||
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 auto ingest case name lock nodes.
|
||||
*/
|
||||
final Path nodeNameAsPath = Paths.get(nodeName);
|
||||
if (!(nodeNameAsPath.toString().contains("\\") || nodeNameAsPath.toString().contains("//"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore case auto ingest log lock nodes and resource lock nodes.
|
||||
*/
|
||||
final String lastNodeNameComponent = nodeNameAsPath.getFileName().toString();
|
||||
if (lastNodeNameComponent.equals(CASE_AUTO_INGEST_LOG_NAME)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore case resources lock nodes.
|
||||
*/
|
||||
if (lastNodeNameComponent.endsWith(RESOURCES_LOCK_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the data from the case directory lock node. This data may not
|
||||
* exist for "legacy" nodes. If it is missing, create it.
|
||||
*/
|
||||
try {
|
||||
CaseNodeData nodeData;
|
||||
byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName);
|
||||
if (nodeBytes != null && nodeBytes.length > 0) {
|
||||
nodeData = new CaseNodeData(nodeBytes);
|
||||
if (nodeData.getVersion() == 0) {
|
||||
/*
|
||||
* Version 0 case node data was only written if errors
|
||||
* occurred during an auto ingest job and consisted of
|
||||
* only the set errors flag.
|
||||
*/
|
||||
nodeData = createNodeDataFromCaseMetadata(nodeName, true);
|
||||
}
|
||||
} else {
|
||||
nodeData = createNodeDataFromCaseMetadata(nodeName, false);
|
||||
}
|
||||
cases.add(nodeData);
|
||||
|
||||
} catch (CoordinationService.CoordinationServiceException | InterruptedException | IOException | ParseException | CaseMetadata.CaseMetadataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex);
|
||||
}
|
||||
|
||||
}
|
||||
return cases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and saves case directory lock coordination service node data from
|
||||
* the metadata file for the case associated with the node.
|
||||
*
|
||||
* @param nodeName The coordination service node name, i.e., the case
|
||||
* directory path.
|
||||
* @param errorsOccurred Whether or not errors occurred during an auto
|
||||
* ingest job for the case.
|
||||
*
|
||||
* @return A CaseNodedata object.
|
||||
*
|
||||
* @throws IOException If there is an error writing the
|
||||
* node data to a byte array.
|
||||
* @throws CaseMetadataException If there is an error reading the
|
||||
* case metadata file.
|
||||
* @throws ParseException If there is an error parsing a date
|
||||
* from the case metadata file.
|
||||
* @throws CoordinationServiceException If there is an error interacting
|
||||
* with the coordination service.
|
||||
* @throws InterruptedException If a coordination service operation
|
||||
* is interrupted.
|
||||
*/
|
||||
private static CaseNodeData createNodeDataFromCaseMetadata(String nodeName, boolean errorsOccurred) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException {
|
||||
CaseNodeData nodeData = null;
|
||||
Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS);
|
||||
File caseDirectory = caseDirectoryPath.toFile();
|
||||
if (caseDirectory.exists()) {
|
||||
File[] files = caseDirectory.listFiles();
|
||||
for (File file : files) {
|
||||
String name = file.getName().toLowerCase();
|
||||
if (name.endsWith(CaseMetadata.getFileExtension())) {
|
||||
CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath()));
|
||||
nodeData = new CaseNodeData(metadata);
|
||||
nodeData.setErrorsOccurred(errorsOccurred);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeData != null) {
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray());
|
||||
return nodeData;
|
||||
} else {
|
||||
throw new IOException(String.format("Could not find case metadata file for %s", nodeName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private MultiUserCaseNodeDataCollector() {
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -1,5 +1,12 @@
|
||||
MultiUserCaseBrowserCustomizer.column.caseDbDeleteStatus=Case Database Deleted
|
||||
MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted
|
||||
MultiUserCaseBrowserCustomizer.column.createTime=Create Time
|
||||
MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted
|
||||
MultiUserCaseBrowserCustomizer.column.directory=Directory
|
||||
MultiUserCaseBrowserCustomizer.column.displayName=Name
|
||||
MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time
|
||||
MultiUserCaseBrowserCustomizer.column.manifestFileZNodesDeleteStatus=Manifest Znodes Deleted
|
||||
MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted
|
||||
MultiUserCaseNode.column.createTime=False
|
||||
MultiUserCaseNode.columnValue.true=True
|
||||
MultiUserCasesBrowserPanel.waitNode.message=Please Wait...
|
||||
|
||||
+12
-2
@@ -141,13 +141,23 @@ public interface MultiUserCaseBrowserCustomizer {
|
||||
"MultiUserCaseBrowserCustomizer.column.displayName=Name",
|
||||
"MultiUserCaseBrowserCustomizer.column.createTime=Create Time",
|
||||
"MultiUserCaseBrowserCustomizer.column.directory=Directory",
|
||||
"MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time"
|
||||
"MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time",
|
||||
"MultiUserCaseBrowserCustomizer.column.manifestFileZNodesDeleteStatus=Manifest Znodes Deleted",
|
||||
"MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted",
|
||||
"MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted",
|
||||
"MultiUserCaseBrowserCustomizer.column.caseDbDeleteStatus=Case Database Deleted",
|
||||
"MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted"
|
||||
})
|
||||
public enum Column {
|
||||
DISPLAY_NAME(Bundle.MultiUserCaseBrowserCustomizer_column_displayName()),
|
||||
CREATE_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_createTime()),
|
||||
DIRECTORY(Bundle.MultiUserCaseBrowserCustomizer_column_directory()),
|
||||
LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime());
|
||||
LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime()),
|
||||
MANIFEST_FILE_ZNODES_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_manifestFileZNodesDeleteStatus()),
|
||||
DATA_SOURCES_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_dataSourcesDeleteStatus()),
|
||||
TEXT_INDEX_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_textIndexDeleteStatus()),
|
||||
CASE_DB_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDbDeleteStatus()),
|
||||
CASE_DIR_DELETE_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDirDeleteStatus());
|
||||
|
||||
private final String displayName;
|
||||
|
||||
|
||||
+33
@@ -25,8 +25,10 @@ import javax.swing.Action;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.DeletedFlags;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrowserCustomizer.Column;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
|
||||
@@ -75,6 +77,21 @@ final class MultiUserCaseNode extends AbstractNode {
|
||||
case LAST_ACCESS_DATE:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, caseNodeData.getLastAccessDate()));
|
||||
break;
|
||||
case MANIFEST_FILE_ZNODES_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_NODES)));
|
||||
break;
|
||||
case DATA_SOURCES_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.DATA_SOURCES)));
|
||||
break;
|
||||
case TEXT_INDEX_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.TEXT_INDEX)));
|
||||
break;
|
||||
case CASE_DB_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.CASE_DB)));
|
||||
break;
|
||||
case CASE_DIR_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.CASE_DIR)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -95,4 +112,20 @@ final class MultiUserCaseNode extends AbstractNode {
|
||||
return customizer.getPreferredAction(caseNodeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interprets the deletion status of part of a case.
|
||||
*
|
||||
* @param flag The coordination service node data deleted items flag
|
||||
* to interpret.
|
||||
*
|
||||
* @return A string stating "True" or "False."
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"MultiUserCaseNode.columnValue.true=True",
|
||||
"MultiUserCaseNode.column.createTime=False",
|
||||
})
|
||||
private String isDeleted(CaseNodeData.DeletedFlags flag) {
|
||||
return caseNodeData.isDeletedFlagSet(flag) ? "True" : "False";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
@@ -43,6 +43,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrows
|
||||
public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int NAME_COLUMN_INDEX = 0;
|
||||
private static final int NAME_COLUMN_WIDTH = 150;
|
||||
private final ExplorerManager explorerManager;
|
||||
private final MultiUserCaseBrowserCustomizer customizer;
|
||||
private final OutlineView outlineView;
|
||||
@@ -103,6 +105,11 @@ public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel impleme
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Give the case name column a greater width.
|
||||
*/
|
||||
outline.getColumnModel().getColumn(NAME_COLUMN_INDEX).setPreferredWidth(NAME_COLUMN_WIDTH);
|
||||
|
||||
/*
|
||||
* Hide the root node and configure the node selection mode.
|
||||
*/
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.MultiUserCaseNodeDataCollector;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeDataCollector;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
@@ -63,9 +63,9 @@ final class MultiUserCasesRootNode extends AbstractNode {
|
||||
@Override
|
||||
protected boolean createKeys(List<CaseNodeData> keys) {
|
||||
try {
|
||||
List<CaseNodeData> caseNodeData = MultiUserCaseNodeDataCollector.getNodeData();
|
||||
List<CaseNodeData> caseNodeData = CaseNodeDataCollector.getNodeData();
|
||||
keys.addAll(caseNodeData);
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
} catch (CoordinationService.CoordinationServiceException | InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get case node data from coodination service", ex);
|
||||
}
|
||||
return true;
|
||||
|
||||
+23
-7
@@ -26,13 +26,29 @@ DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting D
|
||||
DataContentViewerOtherCases.foundInLabel.text=
|
||||
DataContentViewerOtherCases.title=Other Occurrences
|
||||
DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.
|
||||
DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute
|
||||
DataContentViewerOtherCasesModel.csvHeader.case=Case
|
||||
DataContentViewerOtherCasesModel.csvHeader.comment=Comment
|
||||
DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source
|
||||
DataContentViewerOtherCasesModel.csvHeader.device=Device
|
||||
DataContentViewerOtherCasesModel.csvHeader.known=Known
|
||||
DataContentViewerOtherCasesModel.csvHeader.path=Path
|
||||
DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value
|
||||
OccurrencePanel.caseCreatedDateLabel.text=Created Date:
|
||||
OccurrencePanel.caseDetails.text=Case Details
|
||||
OccurrencePanel.caseNameLabel.text=Name:
|
||||
OccurrencePanel.commonProperties.text=Common Properties
|
||||
OccurrencePanel.commonPropertyCommentLabel.text=Comment:
|
||||
OccurrencePanel.commonPropertyKnownStatusLabel.text=Known Status:
|
||||
OccurrencePanel.commonPropertyTypeLabel.text=Type:
|
||||
OccurrencePanel.commonPropertyValueLabel.text=Value:
|
||||
OccurrencePanel.dataSourceDetails.text=Data Source Details
|
||||
OccurrencePanel.dataSourceNameLabel.text=Name:
|
||||
OccurrencePanel.fileDetails.text=File Details
|
||||
OccurrencePanel.filePathLabel.text=File Path:
|
||||
OtherOccurrencesCasesTableModel.case=Case
|
||||
OtherOccurrencesCasesTableModel.noData=No Data.
|
||||
OtherOccurrencesFilesTableModel.attribute=Matched Attribute
|
||||
OtherOccurrencesFilesTableModel.comment=Comment
|
||||
OtherOccurrencesFilesTableModel.dataSource=Data Source
|
||||
OtherOccurrencesFilesTableModel.device=Device
|
||||
OtherOccurrencesFilesTableModel.known=Known
|
||||
OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name
|
||||
OtherOccurrencesDataSourcesTableModel.noData=No Data.
|
||||
OtherOccurrencesFilesTableModel.fileName=File Name
|
||||
OtherOccurrencesFilesTableModel.noData=No Data.
|
||||
OtherOccurrencesFilesTableModel.path=Path
|
||||
OtherOccurrencesFilesTableModel.value=Attribute Value
|
||||
|
||||
+101
-88
@@ -46,11 +46,11 @@
|
||||
</NonVisualComponents>
|
||||
<Properties>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[1500, 10]"/>
|
||||
<Dimension value="[600, 10]"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[1500, 44]"/>
|
||||
<Dimension value="[600, 63]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
@@ -68,123 +68,114 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="otherCasesPanel" alignment="0" pref="1500" max="32767" attributes="0"/>
|
||||
<Component id="tableContainerPanel" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="otherCasesPanel" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="tableContainerPanel" pref="64" max="32767" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="otherCasesPanel">
|
||||
<Container class="javax.swing.JPanel" name="tableContainerPanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[921, 62]"/>
|
||||
<Dimension value="[600, 63]"/>
|
||||
</Property>
|
||||
<Property name="requestFocusEnabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="921" max="32767" attributes="0"/>
|
||||
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="tableContainerPanel" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="earliestCaseLabel" min="-2" pref="161" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="earliestCaseDate" max="32767" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="foundInLabel" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="tablesViewerSplitPane" pref="590" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="62" max="32767" attributes="0"/>
|
||||
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="tableContainerPanel" pref="62" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
|
||||
<Component id="tablesViewerSplitPane" pref="33" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="earliestCaseLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="earliestCaseDate" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="foundInLabel" min="-2" pref="14" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="tableContainerPanel">
|
||||
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[1500, 63]"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="earliestCaseDate">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseDate.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="foundInLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JSplitPane" name="tablesViewerSplitPane">
|
||||
<Properties>
|
||||
<Property name="dividerLocation" type="int" value="450"/>
|
||||
<Property name="resizeWeight" type="double" value="0.5"/>
|
||||
</Properties>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="earliestCaseLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="earliestCaseDate" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="66" max="-2" attributes="0"/>
|
||||
<Component id="foundInLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jSplitPane2" alignment="0" pref="911" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jSplitPane2" pref="31" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="earliestCaseLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="earliestCaseDate" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="foundInLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
|
||||
<Container class="javax.swing.JSplitPane" name="caseDatasourceFileSplitPane">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="earliestCaseDate">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseDate.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="foundInLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JSplitPane" name="jSplitPane2">
|
||||
<Properties>
|
||||
<Property name="dividerLocation" type="int" value="470"/>
|
||||
<Property name="dividerLocation" type="int" value="300"/>
|
||||
<Property name="resizeWeight" type="double" value="0.6"/>
|
||||
<Property name="toolTipText" type="java.lang.String" value=""/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="left"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JSplitPane" name="jSplitPane3">
|
||||
<Container class="javax.swing.JSplitPane" name="caseDatasourceSplitPane">
|
||||
<Properties>
|
||||
<Property name="dividerLocation" type="int" value="150"/>
|
||||
<Property name="resizeWeight" type="double" value="0.5"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
@@ -195,6 +186,11 @@
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="caseScrollPane">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[140, 30]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
@@ -217,6 +213,11 @@
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JScrollPane" name="dataSourceScrollPane">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[140, 30]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
@@ -231,11 +232,8 @@
|
||||
<Component class="javax.swing.JTable" name="dataSourcesTable">
|
||||
<Properties>
|
||||
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
|
||||
<Table columnCount="2" rowCount="0">
|
||||
<Column editable="false" title="Data Source Name" type="java.lang.Object"/>
|
||||
<Column editable="false" title="Device ID" type="java.lang.Object"/>
|
||||
</Table>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="dataSourcesTableModel" type="code"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
@@ -243,10 +241,10 @@
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JScrollPane" name="propertiesTableScrollPane">
|
||||
<Container class="javax.swing.JScrollPane" name="filesTableScrollPane">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[1000, 30]"/>
|
||||
<Dimension value="[140, 30]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
@@ -261,7 +259,7 @@
|
||||
<Properties>
|
||||
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="tableModel" type="code"/>
|
||||
<Connection code="filesTableModel" type="code"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.table.toolTip.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
@@ -273,7 +271,7 @@
|
||||
<ComponentRef name="rightClickPopupMenu"/>
|
||||
</Property>
|
||||
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
|
||||
<JTableSelectionModel selectionMode="1"/>
|
||||
<JTableSelectionModel selectionMode="0"/>
|
||||
</Property>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
|
||||
@@ -284,6 +282,21 @@
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JScrollPane" name="detailsPanelScrollPane">
|
||||
<Properties>
|
||||
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 100]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="right"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
||||
+268
-224
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2015-2019 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");
|
||||
@@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -48,9 +50,6 @@ import static javax.swing.JOptionPane.PLAIN_MESSAGE;
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import org.joda.time.DateTimeZone;
|
||||
@@ -93,50 +92,58 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName());
|
||||
private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noArtifacts());
|
||||
private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noArtifacts());
|
||||
private static final int DEFAULT_MIN_CELL_WIDTH = 15;
|
||||
private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noResultsFound());
|
||||
|
||||
private final OtherOccurrencesFilesTableModel tableModel;
|
||||
private final OtherOccurrencesFilesTableModel filesTableModel;
|
||||
private final OtherOccurrencesCasesTableModel casesTableModel;
|
||||
private final OtherOccurrencesDataSourcesTableModel dataSourcesTableModel;
|
||||
private OccurrencePanel occurrencePanel;
|
||||
private final Collection<CorrelationAttributeInstance> correlationAttributes;
|
||||
private String dataSourceName = "";
|
||||
private String deviceId = "";
|
||||
private String dataSourceName = ""; //the data source of the file which the content viewer is being populated for
|
||||
private String deviceId = ""; //the device id of the data source for the file which the content viewer is being populated for
|
||||
/**
|
||||
* Could be null.
|
||||
*/
|
||||
private AbstractFile file;
|
||||
private AbstractFile file; //the file which the content viewer is being populated for
|
||||
|
||||
/**
|
||||
* Creates new form DataContentViewerOtherCases
|
||||
*/
|
||||
public DataContentViewerOtherCases() {
|
||||
this.tableModel = new OtherOccurrencesFilesTableModel();
|
||||
this.filesTableModel = new OtherOccurrencesFilesTableModel();
|
||||
this.casesTableModel = new OtherOccurrencesCasesTableModel();
|
||||
this.dataSourcesTableModel = new OtherOccurrencesDataSourcesTableModel();
|
||||
this.correlationAttributes = new ArrayList<>();
|
||||
|
||||
occurrencePanel = new OccurrencePanel();
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
|
||||
detailsPanelScrollPane.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent componentEvent) {
|
||||
//when its resized make sure the width of the panel resizes to match the scroll pane width to avoid a horizontal scroll bar
|
||||
occurrencePanel.setPreferredSize(new java.awt.Dimension(detailsPanelScrollPane.getPreferredSize().width, occurrencePanel.getPreferredSize().height));
|
||||
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
||||
}
|
||||
});
|
||||
reset();
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
ActionListener actList = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JMenuItem jmi = (JMenuItem) e.getSource();
|
||||
if (jmi.equals(selectAllMenuItem)) {
|
||||
filesTable.selectAll();
|
||||
} else if (jmi.equals(showCaseDetailsMenuItem)) {
|
||||
showCaseDetails(filesTable.getSelectedRow());
|
||||
} else if (jmi.equals(exportToCSVMenuItem)) {
|
||||
try {
|
||||
saveToCSV();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
|
||||
}
|
||||
} else if (jmi.equals(showCommonalityMenuItem)) {
|
||||
showCommonalityDetails();
|
||||
ActionListener actList = (ActionEvent e) -> {
|
||||
JMenuItem jmi = (JMenuItem) e.getSource();
|
||||
if (jmi.equals(selectAllMenuItem)) {
|
||||
filesTable.selectAll();
|
||||
} else if (jmi.equals(showCaseDetailsMenuItem)) {
|
||||
showCaseDetails(filesTable.getSelectedRow());
|
||||
} else if (jmi.equals(exportToCSVMenuItem)) {
|
||||
try {
|
||||
saveToCSV();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
|
||||
}
|
||||
} else if (jmi.equals(showCommonalityMenuItem)) {
|
||||
showCommonalityDetails();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -145,10 +152,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
showCaseDetailsMenuItem.addActionListener(actList);
|
||||
showCommonalityMenuItem.addActionListener(actList);
|
||||
|
||||
// Set background of every nth row as light grey.
|
||||
TableCellRenderer renderer = new OtherOccurrencesFilesTableCellRenderer();
|
||||
filesTable.setDefaultRenderer(Object.class, renderer);
|
||||
|
||||
// Configure column sorting.
|
||||
TableRowSorter<TableModel> sorter = new TableRowSorter<>(filesTable.getModel());
|
||||
filesTable.setRowSorter(sorter);
|
||||
@@ -162,8 +165,18 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
updateOnDataSourceSelection();
|
||||
}
|
||||
});
|
||||
|
||||
//alows resizing of the 4th section
|
||||
filesTable.getSelectionModel().addListSelectionListener((e) -> {
|
||||
if (Case.isCaseOpen()) {
|
||||
occurrencePanel = new OccurrencePanel();
|
||||
updateOnFileSelection();
|
||||
}
|
||||
});
|
||||
//sort tables alphabetically initially
|
||||
casesTable.getRowSorter().toggleSortOrder(0);
|
||||
dataSourcesTable.getRowSorter().toggleSortOrder(0);
|
||||
filesTable.getRowSorter().toggleSortOrder(0);
|
||||
}
|
||||
|
||||
@Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.",
|
||||
@@ -218,54 +231,43 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
"DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error",
|
||||
"DataContentViewerOtherCases.noOpenCase.errMsg=No open case available."})
|
||||
private void showCaseDetails(int selectedRowViewIdx) {
|
||||
|
||||
String caseDisplayName = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noCaseNameError();
|
||||
String details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails();
|
||||
try {
|
||||
if (-1 != selectedRowViewIdx) {
|
||||
EamDb dbManager = EamDb.getInstance();
|
||||
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
|
||||
OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) tableModel.getRow(selectedRowModelIdx);
|
||||
CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
|
||||
if (eamCasePartial == null) {
|
||||
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
|
||||
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(),
|
||||
caseDisplayName,
|
||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||
return;
|
||||
List<OtherOccurrenceNodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
|
||||
if (!rowList.isEmpty()) {
|
||||
if (rowList.get(0) instanceof OtherOccurrenceNodeInstanceData) {
|
||||
CorrelationCase eamCasePartial = ((OtherOccurrenceNodeInstanceData) rowList.get(0)).getCorrelationAttributeInstance().getCorrelationCase();
|
||||
caseDisplayName = eamCasePartial.getDisplayName();
|
||||
// query case details
|
||||
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
|
||||
if (eamCase != null) {
|
||||
details = eamCase.getCaseDetailsOptionsPaneDialog();
|
||||
} else {
|
||||
details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails();
|
||||
}
|
||||
} else {
|
||||
details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected();
|
||||
}
|
||||
} else {
|
||||
details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference();
|
||||
}
|
||||
caseDisplayName = eamCasePartial.getDisplayName();
|
||||
// query case details
|
||||
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
|
||||
if (eamCase == null) {
|
||||
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
|
||||
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(),
|
||||
caseDisplayName,
|
||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// display case details
|
||||
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
|
||||
eamCase.getCaseDetailsOptionsPaneDialog(),
|
||||
caseDisplayName,
|
||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||
} else {
|
||||
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
|
||||
Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected(),
|
||||
caseDisplayName,
|
||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error loading case details", ex);
|
||||
} finally {
|
||||
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
|
||||
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(),
|
||||
details,
|
||||
caseDisplayName,
|
||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveToCSV() throws NoCurrentCaseException {
|
||||
if (0 != filesTable.getSelectedRowCount()) {
|
||||
if (casesTableModel.getRowCount() > 0) {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now);
|
||||
CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory()));
|
||||
@@ -279,45 +281,46 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
|
||||
selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
|
||||
}
|
||||
|
||||
writeSelectedRowsToFileAsCSV(selectedFile);
|
||||
writeOtherOccurrencesToFileAsCSV(selectedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSelectedRowsToFileAsCSV(File destFile) {
|
||||
StringBuilder content;
|
||||
int[] selectedRowViewIndices = filesTable.getSelectedRows();
|
||||
int colCount = tableModel.getColumnCount();
|
||||
|
||||
@Messages({
|
||||
"DataContentViewerOtherCasesModel.csvHeader.case=Case",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.device=Device",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.known=Known",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.path=Path",
|
||||
"DataContentViewerOtherCasesModel.csvHeader.comment=Comment"
|
||||
})
|
||||
/**
|
||||
* Write data for all cases in the content viewer to a CSV file
|
||||
*/
|
||||
private void writeOtherOccurrencesToFileAsCSV(File destFile) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) {
|
||||
|
||||
// write column names
|
||||
content = new StringBuilder("");
|
||||
for (int colIdx = 0; colIdx < colCount; colIdx++) {
|
||||
content.append('"').append(tableModel.getColumnName(colIdx)).append('"');
|
||||
if (colIdx < (colCount - 1)) {
|
||||
content.append(",");
|
||||
//write headers
|
||||
StringBuilder headers = new StringBuilder("\"");
|
||||
headers.append(Bundle.DataContentViewerOtherCasesModel_csvHeader_case())
|
||||
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_dataSource())
|
||||
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_attribute())
|
||||
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_value())
|
||||
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_known())
|
||||
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_path())
|
||||
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_comment())
|
||||
.append('"').append(System.getProperty("line.separator"));
|
||||
writer.write(headers.toString());
|
||||
//write content
|
||||
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
|
||||
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
|
||||
// get correlation and reference set instances from DB
|
||||
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
|
||||
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
|
||||
writer.write(nodeData.toCsvString());
|
||||
}
|
||||
}
|
||||
|
||||
content.append(System.getProperty("line.separator"));
|
||||
writer.write(content.toString());
|
||||
|
||||
// write rows
|
||||
for (int rowViewIdx : selectedRowViewIndices) {
|
||||
content = new StringBuilder("");
|
||||
for (int colIdx = 0; colIdx < colCount; colIdx++) {
|
||||
int rowModelIdx = filesTable.convertRowIndexToModel(rowViewIdx);
|
||||
content.append('"').append(tableModel.getValueAt(rowModelIdx, colIdx)).append('"');
|
||||
if (colIdx < (colCount - 1)) {
|
||||
content.append(",");
|
||||
}
|
||||
}
|
||||
content.append(System.getProperty("line.separator"));
|
||||
writer.write(content.toString());
|
||||
}
|
||||
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex);
|
||||
}
|
||||
@@ -329,11 +332,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
private void reset() {
|
||||
// start with empty table
|
||||
casesTableModel.clearTable();
|
||||
((DefaultTableModel) dataSourcesTable.getModel()).setRowCount(0);
|
||||
tableModel.clearTable();
|
||||
dataSourcesTableModel.clearTable();
|
||||
filesTableModel.clearTable();
|
||||
correlationAttributes.clear();
|
||||
earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable());
|
||||
foundInLabel.setText("");
|
||||
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
|
||||
occurrencePanel = new OccurrencePanel();
|
||||
occurrencePanel.getPreferredSize();
|
||||
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -364,6 +371,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
@Override
|
||||
public int isPreferred(Node node) {
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,7 +566,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
try {
|
||||
final Case openCase = Case.getCurrentCaseThrows();
|
||||
String caseUUID = openCase.getName();
|
||||
|
||||
HashMap<UniquePathKey, OtherOccurrenceNodeInstanceData> nodeDataMap = new HashMap<>();
|
||||
|
||||
if (EamDb.isEnabled()) {
|
||||
@@ -583,7 +590,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (corAttr.getCorrelationType().getDisplayName().equals("Files")) {
|
||||
List<AbstractFile> caseDbFiles = getCaseDbMatches(corAttr, openCase);
|
||||
|
||||
@@ -591,7 +597,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeDataMap;
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS
|
||||
@@ -748,14 +753,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
|
||||
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName());
|
||||
LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
|
||||
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.WARNING, "No current case open for other occurrences");
|
||||
LOGGER.log(Level.WARNING, "No current case open for other occurrences", ex);
|
||||
}
|
||||
}
|
||||
totalCount++;
|
||||
@@ -770,7 +775,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
} else if (caseCount == 0) {
|
||||
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
||||
}
|
||||
setColumnWidths();
|
||||
setEarliestCaseDate();
|
||||
foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), totalCount, caseCount, dataSources.size()));
|
||||
if (caseCount > 0) {
|
||||
@@ -791,33 +795,40 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
*/
|
||||
private void updateOnCaseSelection() {
|
||||
int[] selectedCaseIndexes = casesTable.getSelectedRows();
|
||||
DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel();
|
||||
dataSourceModel.setRowCount(0);
|
||||
tableModel.clearTable();
|
||||
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
|
||||
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
|
||||
dataSourcesTableModel.clearTable();
|
||||
filesTableModel.clearTable();
|
||||
|
||||
// get correlation and reference set instances from DB
|
||||
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
|
||||
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
|
||||
for (int selectedRow : selectedCaseIndexes) {
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
|
||||
&& ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
|
||||
dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()});
|
||||
if (selectedCaseIndexes.length == 0) {
|
||||
//special case when no cases are selected
|
||||
occurrencePanel = new OccurrencePanel();
|
||||
occurrencePanel.getPreferredSize();
|
||||
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
||||
} else {
|
||||
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
|
||||
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
|
||||
|
||||
// get correlation and reference set instances from DB
|
||||
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
|
||||
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
|
||||
for (int selectedRow : selectedCaseIndexes) {
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
|
||||
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
|
||||
dataSourcesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else {
|
||||
dataSourcesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else {
|
||||
dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()});
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dataSourcesTable.getRowCount() > 0) {
|
||||
dataSourcesTable.setRowSelectionInterval(0, 0);
|
||||
if (dataSourcesTable.getRowCount() > 0) {
|
||||
dataSourcesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,9 +838,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
*/
|
||||
private void updateOnDataSourceSelection() {
|
||||
int[] selectedCaseIndexes = casesTable.getSelectedRows();
|
||||
DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel();
|
||||
int[] selectedDataSources = dataSourcesTable.getSelectedRows();
|
||||
tableModel.clearTable();
|
||||
filesTableModel.clearTable();
|
||||
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
|
||||
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
|
||||
|
||||
@@ -841,43 +851,94 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)) != null
|
||||
&& ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
|
||||
&& dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) {
|
||||
tableModel.addNodeData(nodeData);
|
||||
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
|
||||
&& dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
|
||||
filesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else {
|
||||
if (dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) {
|
||||
tableModel.addNodeData(nodeData);
|
||||
if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
|
||||
filesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName());
|
||||
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filesTable.getRowCount() > 0) {
|
||||
filesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust column widths to their preferred values.
|
||||
* Update the data displayed in the details section to be correct for the
|
||||
* currently selected File
|
||||
*/
|
||||
private void setColumnWidths() {
|
||||
for (int idx = 0; idx < tableModel.getColumnCount(); idx++) {
|
||||
TableColumn column = filesTable.getColumnModel().getColumn(idx);
|
||||
column.setMinWidth(DEFAULT_MIN_CELL_WIDTH);
|
||||
int columnWidth = tableModel.getColumnPreferredWidth(idx);
|
||||
if (columnWidth > 0) {
|
||||
column.setPreferredWidth(columnWidth);
|
||||
private void updateOnFileSelection() {
|
||||
if (filesTable.getSelectedRowCount() == 1) {
|
||||
//if there is one file selected update the deatils to show the data for that file
|
||||
occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow())));
|
||||
} else if (dataSourcesTable.getSelectedRowCount() == 1) {
|
||||
//if no files were selected and only one data source is selected update the information to reflect the data source
|
||||
String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()));
|
||||
String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString();
|
||||
String caseCreatedDate = "";
|
||||
for (int row : casesTable.getSelectedRows()) {
|
||||
if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) {
|
||||
caseCreatedDate = getCaseCreatedDate(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int idx = 0; idx < dataSourcesTable.getColumnCount(); idx++) {
|
||||
if (dataSourcesTable.getColumnModel().getColumn(idx).getHeaderValue().toString().equals(Bundle.DataContentViewerOtherCases_dataSources_header_text())) {
|
||||
dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(100);
|
||||
occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName);
|
||||
} else if (casesTable.getSelectedRowCount() == 1) {
|
||||
//if no files were selected and a number of data source other than 1 are selected
|
||||
//update the information to reflect the case
|
||||
String createdDate = "";
|
||||
String caseName = "";
|
||||
if (casesTable.getRowCount() > 0) {
|
||||
caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString();
|
||||
}
|
||||
if (caseName.isEmpty()) {
|
||||
occurrencePanel = new OccurrencePanel();
|
||||
} else {
|
||||
dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(210);
|
||||
createdDate = getCaseCreatedDate(casesTable.getSelectedRow());
|
||||
occurrencePanel = new OccurrencePanel(caseName, createdDate);
|
||||
}
|
||||
} else {
|
||||
//else display an empty details area
|
||||
occurrencePanel = new OccurrencePanel();
|
||||
}
|
||||
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
|
||||
occurrencePanel.getPreferredSize();
|
||||
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date a case was created
|
||||
*
|
||||
* @param caseTableRowIdx the row from the casesTable representing the case
|
||||
*
|
||||
* @return A string representing the date the case was created or an empty
|
||||
* string if the date could not be determined
|
||||
*/
|
||||
private String getCaseCreatedDate(int caseTableRowIdx) {
|
||||
try {
|
||||
if (EamDb.isEnabled()) {
|
||||
CorrelationCase partialCase;
|
||||
partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx));
|
||||
if (partialCase == null){
|
||||
return "";
|
||||
}
|
||||
return EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
|
||||
} else {
|
||||
return Case.getCurrentCase().getCreatedDate();
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.WARNING, "Error getting case created date for row: " + caseTableRowIdx, ex);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -895,19 +956,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
showCaseDetailsMenuItem = new javax.swing.JMenuItem();
|
||||
showCommonalityMenuItem = new javax.swing.JMenuItem();
|
||||
CSVFileChooser = new javax.swing.JFileChooser();
|
||||
otherCasesPanel = new javax.swing.JPanel();
|
||||
tableContainerPanel = new javax.swing.JPanel();
|
||||
earliestCaseLabel = new javax.swing.JLabel();
|
||||
earliestCaseDate = new javax.swing.JLabel();
|
||||
foundInLabel = new javax.swing.JLabel();
|
||||
jSplitPane2 = new javax.swing.JSplitPane();
|
||||
jSplitPane3 = new javax.swing.JSplitPane();
|
||||
tablesViewerSplitPane = new javax.swing.JSplitPane();
|
||||
caseDatasourceFileSplitPane = new javax.swing.JSplitPane();
|
||||
caseDatasourceSplitPane = new javax.swing.JSplitPane();
|
||||
caseScrollPane = new javax.swing.JScrollPane();
|
||||
casesTable = new javax.swing.JTable();
|
||||
dataSourceScrollPane = new javax.swing.JScrollPane();
|
||||
dataSourcesTable = new javax.swing.JTable();
|
||||
propertiesTableScrollPane = new javax.swing.JScrollPane();
|
||||
filesTableScrollPane = new javax.swing.JScrollPane();
|
||||
filesTable = new javax.swing.JTable();
|
||||
detailsPanelScrollPane = new javax.swing.JScrollPane();
|
||||
|
||||
rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
|
||||
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
|
||||
@@ -931,13 +993,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N
|
||||
rightClickPopupMenu.add(showCommonalityMenuItem);
|
||||
|
||||
setMinimumSize(new java.awt.Dimension(1500, 10));
|
||||
setMinimumSize(new java.awt.Dimension(600, 10));
|
||||
setOpaque(false);
|
||||
setPreferredSize(new java.awt.Dimension(1500, 44));
|
||||
setPreferredSize(new java.awt.Dimension(600, 63));
|
||||
|
||||
otherCasesPanel.setPreferredSize(new java.awt.Dimension(921, 62));
|
||||
|
||||
tableContainerPanel.setPreferredSize(new java.awt.Dimension(1500, 63));
|
||||
tableContainerPanel.setPreferredSize(new java.awt.Dimension(600, 63));
|
||||
tableContainerPanel.setRequestFocusEnabled(false);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N
|
||||
earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N
|
||||
@@ -946,49 +1007,50 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(foundInLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.foundInLabel.text")); // NOI18N
|
||||
|
||||
jSplitPane2.setDividerLocation(470);
|
||||
tablesViewerSplitPane.setDividerLocation(450);
|
||||
tablesViewerSplitPane.setResizeWeight(0.5);
|
||||
|
||||
jSplitPane3.setDividerLocation(150);
|
||||
caseDatasourceFileSplitPane.setDividerLocation(300);
|
||||
caseDatasourceFileSplitPane.setResizeWeight(0.6);
|
||||
caseDatasourceFileSplitPane.setToolTipText("");
|
||||
|
||||
caseDatasourceSplitPane.setDividerLocation(150);
|
||||
caseDatasourceSplitPane.setResizeWeight(0.5);
|
||||
|
||||
caseScrollPane.setPreferredSize(new java.awt.Dimension(140, 30));
|
||||
|
||||
casesTable.setAutoCreateRowSorter(true);
|
||||
casesTable.setModel(casesTableModel);
|
||||
caseScrollPane.setViewportView(casesTable);
|
||||
|
||||
jSplitPane3.setLeftComponent(caseScrollPane);
|
||||
caseDatasourceSplitPane.setLeftComponent(caseScrollPane);
|
||||
|
||||
dataSourceScrollPane.setPreferredSize(new java.awt.Dimension(140, 30));
|
||||
|
||||
dataSourcesTable.setAutoCreateRowSorter(true);
|
||||
dataSourcesTable.setModel(new javax.swing.table.DefaultTableModel(
|
||||
new Object [][] {
|
||||
|
||||
},
|
||||
new String [] {
|
||||
"Data Source Name", "Device ID"
|
||||
}
|
||||
) {
|
||||
boolean[] canEdit = new boolean [] {
|
||||
false, false
|
||||
};
|
||||
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
return canEdit [columnIndex];
|
||||
}
|
||||
});
|
||||
dataSourcesTable.setModel(dataSourcesTableModel);
|
||||
dataSourceScrollPane.setViewportView(dataSourcesTable);
|
||||
|
||||
jSplitPane3.setRightComponent(dataSourceScrollPane);
|
||||
caseDatasourceSplitPane.setRightComponent(dataSourceScrollPane);
|
||||
|
||||
jSplitPane2.setLeftComponent(jSplitPane3);
|
||||
caseDatasourceFileSplitPane.setLeftComponent(caseDatasourceSplitPane);
|
||||
|
||||
propertiesTableScrollPane.setPreferredSize(new java.awt.Dimension(1000, 30));
|
||||
filesTableScrollPane.setPreferredSize(new java.awt.Dimension(140, 30));
|
||||
|
||||
filesTable.setAutoCreateRowSorter(true);
|
||||
filesTable.setModel(tableModel);
|
||||
filesTable.setModel(filesTableModel);
|
||||
filesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N
|
||||
filesTable.setComponentPopupMenu(rightClickPopupMenu);
|
||||
filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION);
|
||||
propertiesTableScrollPane.setViewportView(filesTable);
|
||||
filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
filesTableScrollPane.setViewportView(filesTable);
|
||||
|
||||
jSplitPane2.setRightComponent(propertiesTableScrollPane);
|
||||
caseDatasourceFileSplitPane.setRightComponent(filesTableScrollPane);
|
||||
|
||||
tablesViewerSplitPane.setLeftComponent(caseDatasourceFileSplitPane);
|
||||
|
||||
detailsPanelScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
detailsPanelScrollPane.setPreferredSize(new java.awt.Dimension(200, 100));
|
||||
tablesViewerSplitPane.setRightComponent(detailsPanelScrollPane);
|
||||
|
||||
javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel);
|
||||
tableContainerPanel.setLayout(tableContainerPanelLayout);
|
||||
@@ -997,65 +1059,49 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
.addGroup(tableContainerPanelLayout.createSequentialGroup()
|
||||
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(tableContainerPanelLayout.createSequentialGroup()
|
||||
.addComponent(earliestCaseLabel)
|
||||
.addComponent(earliestCaseLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 161, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(earliestCaseDate)
|
||||
.addGap(66, 66, 66)
|
||||
.addComponent(foundInLabel))
|
||||
.addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 911, Short.MAX_VALUE))
|
||||
.addComponent(earliestCaseDate, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(foundInLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 590, Short.MAX_VALUE))
|
||||
.addContainerGap())
|
||||
);
|
||||
tableContainerPanelLayout.setVerticalGroup(
|
||||
tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup()
|
||||
.addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 31, Short.MAX_VALUE)
|
||||
.addGroup(tableContainerPanelLayout.createSequentialGroup()
|
||||
.addGap(0, 0, 0)
|
||||
.addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 33, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(earliestCaseLabel)
|
||||
.addComponent(earliestCaseDate)
|
||||
.addComponent(foundInLabel))
|
||||
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(earliestCaseLabel)
|
||||
.addComponent(earliestCaseDate))
|
||||
.addComponent(foundInLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel);
|
||||
otherCasesPanel.setLayout(otherCasesPanelLayout);
|
||||
otherCasesPanelLayout.setHorizontalGroup(
|
||||
otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 921, Short.MAX_VALUE)
|
||||
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(otherCasesPanelLayout.createSequentialGroup()
|
||||
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(0, 0, 0)))
|
||||
);
|
||||
otherCasesPanelLayout.setVerticalGroup(
|
||||
otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 62, Short.MAX_VALUE)
|
||||
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(otherCasesPanelLayout.createSequentialGroup()
|
||||
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 62, Short.MAX_VALUE)
|
||||
.addGap(0, 0, 0)))
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE)
|
||||
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 64, Short.MAX_VALUE)
|
||||
.addGap(0, 0, 0))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible
|
||||
boolean enableCentralRepoActions = false;
|
||||
|
||||
if (EamDb.isEnabled() && filesTable.getSelectedRowCount() == 1) {
|
||||
int rowIndex = filesTable.getSelectedRow();
|
||||
OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex);
|
||||
if (selectedNode instanceof OtherOccurrenceNodeInstanceData) {
|
||||
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedNode;
|
||||
List<OtherOccurrenceNodeData> selectedFile = filesTableModel.getListOfNodesForFile(rowIndex);
|
||||
if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof OtherOccurrenceNodeInstanceData) {
|
||||
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedFile.get(0);
|
||||
enableCentralRepoActions = instanceData.isCentralRepoNode();
|
||||
}
|
||||
}
|
||||
@@ -1065,24 +1111,25 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JFileChooser CSVFileChooser;
|
||||
private javax.swing.JSplitPane caseDatasourceFileSplitPane;
|
||||
private javax.swing.JSplitPane caseDatasourceSplitPane;
|
||||
private javax.swing.JScrollPane caseScrollPane;
|
||||
private javax.swing.JTable casesTable;
|
||||
private javax.swing.JScrollPane dataSourceScrollPane;
|
||||
private javax.swing.JTable dataSourcesTable;
|
||||
private javax.swing.JScrollPane detailsPanelScrollPane;
|
||||
private javax.swing.JLabel earliestCaseDate;
|
||||
private javax.swing.JLabel earliestCaseLabel;
|
||||
private javax.swing.JMenuItem exportToCSVMenuItem;
|
||||
private javax.swing.JTable filesTable;
|
||||
private javax.swing.JScrollPane filesTableScrollPane;
|
||||
private javax.swing.JLabel foundInLabel;
|
||||
private javax.swing.JSplitPane jSplitPane2;
|
||||
private javax.swing.JSplitPane jSplitPane3;
|
||||
private javax.swing.JPanel otherCasesPanel;
|
||||
private javax.swing.JScrollPane propertiesTableScrollPane;
|
||||
private javax.swing.JPopupMenu rightClickPopupMenu;
|
||||
private javax.swing.JMenuItem selectAllMenuItem;
|
||||
private javax.swing.JMenuItem showCaseDetailsMenuItem;
|
||||
private javax.swing.JMenuItem showCommonalityMenuItem;
|
||||
private javax.swing.JPanel tableContainerPanel;
|
||||
private javax.swing.JSplitPane tablesViewerSplitPane;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
/**
|
||||
@@ -1119,9 +1166,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
//int hash = 7;
|
||||
//hash = 67 * hash + this.dataSourceID.hashCode();
|
||||
//hash = 67 * hash + this.filePath.hashCode();
|
||||
return Objects.hash(getDataSourceID(), getFilePath(), getType());
|
||||
}
|
||||
|
||||
|
||||
Executable → Regular
+4
-14
@@ -2,8 +2,8 @@
|
||||
|
||||
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[1002, 444]"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[50, 30]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
@@ -16,18 +16,8 @@
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,68,0,0,0,-116"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="902" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="444" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
</Form>
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 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.centralrepository.contentviewer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Panel for displaying other occurrence details.
|
||||
*/
|
||||
final class OccurrencePanel extends javax.swing.JPanel {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OccurrencePanel.class.getName());
|
||||
private static final int LEFT_INSET = 10;
|
||||
private static final int RIGHT_INSET = 10;
|
||||
private static final int TOP_INSET = 10;
|
||||
private static final int BOTTOM_INSET = 10;
|
||||
private static final int VERTICAL_GAP = 6;
|
||||
private static final int HORIZONTAL_GAP = 4;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int gridY = 0;
|
||||
private final List<OtherOccurrenceNodeData> nodeDataList;
|
||||
private final Map<String, String> caseNamesAndDates = new HashMap<>();
|
||||
private final Set<String> dataSourceNames = new HashSet<>();
|
||||
private final Set<String> filePaths = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Construct an empty OccurrencePanel
|
||||
*/
|
||||
OccurrencePanel() {
|
||||
nodeDataList = new ArrayList<>();
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an OccurrencePanel which will display only Case information
|
||||
*
|
||||
* @param caseName the name of the case
|
||||
* @param caseCreatedDate the date the case was created
|
||||
*/
|
||||
OccurrencePanel(String caseName, String caseCreatedDate) {
|
||||
nodeDataList = new ArrayList<>();
|
||||
caseNamesAndDates.put(caseName, caseCreatedDate);
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an OccurrencePanel which will display only case and data source
|
||||
* information
|
||||
*
|
||||
* @param caseName the name of the case
|
||||
* @param caseCreatedDate the date the case was created
|
||||
* @param dataSourceName the name of the data source
|
||||
*/
|
||||
OccurrencePanel(String caseName, String caseCreatedDate, String dataSourceName) {
|
||||
nodeDataList = new ArrayList<>();
|
||||
caseNamesAndDates.put(caseName, caseCreatedDate);
|
||||
dataSourceNames.add(dataSourceName);
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a OccurrencePanel which will display details for all other
|
||||
* occurrences associated with a file
|
||||
*
|
||||
* @param nodeDataList the list of OtherOccurrenceNodeData representing
|
||||
* common properties for the file
|
||||
*/
|
||||
OccurrencePanel(List<OtherOccurrenceNodeData> nodeDataList) {
|
||||
this.nodeDataList = nodeDataList;
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do all the construction of gui elements and adding of the appropriate
|
||||
* elements to the gridbaglayout
|
||||
*/
|
||||
private void customizeComponents() {
|
||||
initComponents();
|
||||
if (!this.nodeDataList.isEmpty()) {
|
||||
//if addInstanceDetails is going to be called it should be called
|
||||
// before addFileDetails, addDataSourceDetails, and addCaseDetails
|
||||
//because it also collects the information they display
|
||||
addInstanceDetails();
|
||||
if (!filePaths.isEmpty()) {
|
||||
addFileDetails();
|
||||
}
|
||||
}
|
||||
if (!dataSourceNames.isEmpty()) {
|
||||
addDataSourceDetails();
|
||||
}
|
||||
if (!caseNamesAndDates.keySet().isEmpty()) {
|
||||
addCaseDetails();
|
||||
}
|
||||
//add filler to keep everything else at the top
|
||||
addItemToBag(gridY, 0, 0, 0, new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)));
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"OccurrencePanel.commonProperties.text=Common Properties",
|
||||
"OccurrencePanel.commonPropertyTypeLabel.text=Type:",
|
||||
"OccurrencePanel.commonPropertyValueLabel.text=Value:",
|
||||
"OccurrencePanel.commonPropertyKnownStatusLabel.text=Known Status:",
|
||||
"OccurrencePanel.commonPropertyCommentLabel.text=Comment:"
|
||||
})
|
||||
/**
|
||||
* Add the Common Property instance details to the gridbaglayout supports
|
||||
* adding multiple common properties
|
||||
*
|
||||
* Also collects the case, data source, and file path information to be
|
||||
* displayed
|
||||
*/
|
||||
private void addInstanceDetails() {
|
||||
javax.swing.JLabel commonPropertiesLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(commonPropertiesLabel, Bundle.OccurrencePanel_commonProperties_text());
|
||||
commonPropertiesLabel.setFont(commonPropertiesLabel.getFont().deriveFont(Font.BOLD, commonPropertiesLabel.getFont().getSize()));
|
||||
addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel);
|
||||
gridY++;
|
||||
//for each other occurrence
|
||||
for (OtherOccurrenceNodeData occurrence : nodeDataList) {
|
||||
if (occurrence instanceof OtherOccurrenceNodeInstanceData) {
|
||||
String type = ((OtherOccurrenceNodeInstanceData) occurrence).getType();
|
||||
if (!type.isEmpty()) {
|
||||
javax.swing.JLabel typeLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(typeLabel, Bundle.OccurrencePanel_commonPropertyTypeLabel_text());
|
||||
addItemToBag(gridY, 0, VERTICAL_GAP, 0, typeLabel);
|
||||
javax.swing.JLabel typeFieldValue = new javax.swing.JLabel();
|
||||
typeFieldValue.setText(type);
|
||||
addItemToBag(gridY, 1, VERTICAL_GAP, 0, typeFieldValue);
|
||||
gridY++;
|
||||
}
|
||||
String value = ((OtherOccurrenceNodeInstanceData) occurrence).getValue();
|
||||
if (!value.isEmpty()) {
|
||||
javax.swing.JLabel valueLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(valueLabel, Bundle.OccurrencePanel_commonPropertyValueLabel_text());
|
||||
addItemToBag(gridY, 0, 0, 0, valueLabel);
|
||||
javax.swing.JLabel valueFieldValue = new javax.swing.JLabel();
|
||||
valueFieldValue.setText(value);
|
||||
addItemToBag(gridY, 1, 0, 0, valueFieldValue);
|
||||
gridY++;
|
||||
}
|
||||
TskData.FileKnown knownStatus = ((OtherOccurrenceNodeInstanceData) occurrence).getKnown();
|
||||
javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text());
|
||||
addItemToBag(gridY, 0, 0, 0, knownStatusLabel);
|
||||
javax.swing.JLabel knownStatusValue = new javax.swing.JLabel();
|
||||
knownStatusValue.setText(knownStatus.toString());
|
||||
if (knownStatus == TskData.FileKnown.BAD) {
|
||||
knownStatusValue.setForeground(Color.RED);
|
||||
}
|
||||
addItemToBag(gridY, 1, 0, 0, knownStatusValue);
|
||||
gridY++;
|
||||
String comment = ((OtherOccurrenceNodeInstanceData) occurrence).getComment();
|
||||
if (!comment.isEmpty()) {
|
||||
javax.swing.JLabel commentLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text());
|
||||
addItemToBag(gridY, 0, 0, VERTICAL_GAP, commentLabel);
|
||||
javax.swing.JTextArea commentValue = new javax.swing.JTextArea();
|
||||
commentValue.setText(comment);
|
||||
commentValue.setEditable(false);
|
||||
commentValue.setColumns(20);
|
||||
commentValue.setLineWrap(true);
|
||||
commentValue.setRows(3);
|
||||
commentValue.setTabSize(4);
|
||||
commentValue.setWrapStyleWord(true);
|
||||
commentValue.setBorder(javax.swing.BorderFactory.createEtchedBorder());
|
||||
commentValue.setBackground(javax.swing.UIManager.getDefaults().getColor("TextArea.disabledBackground"));
|
||||
addItemToBag(gridY, 1, 0, VERTICAL_GAP, commentValue);
|
||||
gridY++;
|
||||
}
|
||||
String caseDate = "";
|
||||
try {
|
||||
OtherOccurrenceNodeInstanceData nodeData = ((OtherOccurrenceNodeInstanceData) occurrence);
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (EamDb.isEnabled()) {
|
||||
CorrelationCase partialCase = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
|
||||
caseDate = EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
|
||||
}
|
||||
} else {
|
||||
caseDate = Case.getCurrentCase().getCreatedDate();
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.WARNING, "Error getting case created date for other occurrence content viewer", ex);
|
||||
}
|
||||
//Collect the data that is necessary for the other sections
|
||||
caseNamesAndDates.put(((OtherOccurrenceNodeInstanceData) occurrence).getCaseName(), caseDate);
|
||||
dataSourceNames.add(((OtherOccurrenceNodeInstanceData) occurrence).getDataSourceName());
|
||||
filePaths.add(((OtherOccurrenceNodeInstanceData) occurrence).getFilePath());
|
||||
}
|
||||
}
|
||||
//end for each
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"OccurrencePanel.fileDetails.text=File Details",
|
||||
"OccurrencePanel.filePathLabel.text=File Path:"
|
||||
})
|
||||
/**
|
||||
* Add the File specific details such as file path to the gridbaglayout
|
||||
*/
|
||||
private void addFileDetails() {
|
||||
String filePath = filePaths.size() > 1 ? "" : filePaths.iterator().next();
|
||||
if (!filePath.isEmpty()) {
|
||||
javax.swing.JLabel fileDetailsLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(fileDetailsLabel, Bundle.OccurrencePanel_fileDetails_text());
|
||||
fileDetailsLabel.setFont(fileDetailsLabel.getFont().deriveFont(Font.BOLD, fileDetailsLabel.getFont().getSize()));
|
||||
addItemToBag(gridY, 0, TOP_INSET, 0, fileDetailsLabel);
|
||||
gridY++;
|
||||
javax.swing.JLabel filePathLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(filePathLabel, Bundle.OccurrencePanel_filePathLabel_text());
|
||||
addItemToBag(gridY, 0, VERTICAL_GAP, VERTICAL_GAP, filePathLabel);
|
||||
javax.swing.JTextArea filePathValue = new javax.swing.JTextArea();
|
||||
filePathValue.setText(filePath);
|
||||
filePathValue.setEditable(false);
|
||||
filePathValue.setColumns(20);
|
||||
filePathValue.setLineWrap(true);
|
||||
filePathValue.setRows(3);
|
||||
filePathValue.setTabSize(4);
|
||||
filePathValue.setWrapStyleWord(true);
|
||||
filePathValue.setBorder(javax.swing.BorderFactory.createEtchedBorder());
|
||||
filePathValue.setBackground(javax.swing.UIManager.getDefaults().getColor("TextArea.disabledBackground"));
|
||||
addItemToBag(gridY, 1, VERTICAL_GAP, VERTICAL_GAP, filePathValue);
|
||||
gridY++;
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"OccurrencePanel.dataSourceDetails.text=Data Source Details",
|
||||
"OccurrencePanel.dataSourceNameLabel.text=Name:"
|
||||
})
|
||||
/**
|
||||
* Add the data source specific details such as data source name to the
|
||||
* gridbaglayout
|
||||
*/
|
||||
private void addDataSourceDetails() {
|
||||
String dataSourceName = dataSourceNames.size() > 1 ? "" : dataSourceNames.iterator().next();
|
||||
if (!dataSourceName.isEmpty()) {
|
||||
javax.swing.JLabel dataSourceDetailsLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(dataSourceDetailsLabel, Bundle.OccurrencePanel_dataSourceDetails_text());
|
||||
dataSourceDetailsLabel.setFont(dataSourceDetailsLabel.getFont().deriveFont(Font.BOLD, dataSourceDetailsLabel.getFont().getSize()));
|
||||
addItemToBag(gridY, 0, TOP_INSET, 0, dataSourceDetailsLabel);
|
||||
gridY++;
|
||||
javax.swing.JLabel dataSourceNameLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(dataSourceNameLabel, Bundle.OccurrencePanel_dataSourceNameLabel_text());
|
||||
addItemToBag(gridY, 0, VERTICAL_GAP, VERTICAL_GAP, dataSourceNameLabel);
|
||||
javax.swing.JLabel dataSourceNameValue = new javax.swing.JLabel();
|
||||
dataSourceNameValue.setText(dataSourceName);
|
||||
addItemToBag(gridY, 1, VERTICAL_GAP, VERTICAL_GAP, dataSourceNameValue);
|
||||
gridY++;
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"OccurrencePanel.caseDetails.text=Case Details",
|
||||
"OccurrencePanel.caseNameLabel.text=Name:",
|
||||
"OccurrencePanel.caseCreatedDateLabel.text=Created Date:"
|
||||
})
|
||||
/**
|
||||
* Add the case specific details such as case name to the gridbaglayout
|
||||
*/
|
||||
private void addCaseDetails() {
|
||||
javax.swing.JLabel caseDetailsLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(caseDetailsLabel, Bundle.OccurrencePanel_caseDetails_text());
|
||||
caseDetailsLabel.setFont(caseDetailsLabel.getFont().deriveFont(Font.BOLD, caseDetailsLabel.getFont().getSize()));
|
||||
addItemToBag(gridY, 0, TOP_INSET, 0, caseDetailsLabel);
|
||||
gridY++;
|
||||
String caseName = caseNamesAndDates.keySet().size() > 1 ? "" : caseNamesAndDates.keySet().iterator().next();
|
||||
if (!caseName.isEmpty()) {
|
||||
javax.swing.JLabel caseNameLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(caseNameLabel, Bundle.OccurrencePanel_caseNameLabel_text());
|
||||
addItemToBag(gridY, 0, VERTICAL_GAP, 0, caseNameLabel);
|
||||
javax.swing.JLabel caseNameValue = new javax.swing.JLabel();
|
||||
caseNameValue.setText(caseName);
|
||||
addItemToBag(gridY, 1, VERTICAL_GAP, 0, caseNameValue);
|
||||
gridY++;
|
||||
}
|
||||
String caseCreatedDate = caseNamesAndDates.keySet().size() > 1 ? "" : caseNamesAndDates.get(caseName);
|
||||
if (caseCreatedDate != null && !caseCreatedDate.isEmpty()) {
|
||||
javax.swing.JLabel caseCreatedLabel = new javax.swing.JLabel();
|
||||
org.openide.awt.Mnemonics.setLocalizedText(caseCreatedLabel, Bundle.OccurrencePanel_caseCreatedDateLabel_text());
|
||||
addItemToBag(gridY, 0, 0, BOTTOM_INSET, caseCreatedLabel);
|
||||
javax.swing.JLabel caseCreatedValue = new javax.swing.JLabel();
|
||||
caseCreatedValue.setText(caseCreatedDate);
|
||||
addItemToBag(gridY, 1, 0, BOTTOM_INSET, caseCreatedValue);
|
||||
gridY++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JComponent to the gridbaglayout
|
||||
*
|
||||
* @param gridYLocation the row number the item should be added at
|
||||
* @param gridXLocation the column number the item should be added at
|
||||
* @param topInset the gap from the top of the cell which should exist
|
||||
* @param bottomInset the gap from the bottom of the cell which should
|
||||
* exist
|
||||
* @param item the JComponent to add to the gridbaglayout
|
||||
*/
|
||||
private void addItemToBag(int gridYLocation, int gridXLocation, int topInset, int bottomInset, javax.swing.JComponent item) {
|
||||
java.awt.GridBagConstraints gridBagConstraints;
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = gridXLocation;
|
||||
gridBagConstraints.gridy = gridYLocation;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
|
||||
int leftInset = LEFT_INSET;
|
||||
int rightInset = HORIZONTAL_GAP;
|
||||
//change formating a bit if it is the value instead of the label
|
||||
if (gridXLocation == 1) {
|
||||
leftInset = 0;
|
||||
rightInset = RIGHT_INSET;
|
||||
gridBagConstraints.weightx = 0.1;
|
||||
gridBagConstraints.gridwidth = 2;
|
||||
}
|
||||
gridBagConstraints.insets = new java.awt.Insets(topInset, leftInset, bottomInset, rightInset);
|
||||
//if the item is a filler item ensure it will resize vertically
|
||||
if (item instanceof javax.swing.Box.Filler) {
|
||||
gridBagConstraints.weighty = 0.1;
|
||||
}
|
||||
add(item, gridBagConstraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
setMinimumSize(new java.awt.Dimension(50, 30));
|
||||
setPreferredSize(null);
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
+80
-31
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Copyright 2018-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -31,11 +31,12 @@ import org.sleuthkit.datamodel.TskDataException;
|
||||
* Class for populating the Other Occurrences tab
|
||||
*/
|
||||
class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
||||
|
||||
|
||||
// For now hard code the string for the central repo files type, since
|
||||
// getting it dynamically can fail.
|
||||
private static final String FILE_TYPE_STR = "Files";
|
||||
|
||||
private static final String CSV_ITEM_SEPARATOR = "\",\"";
|
||||
|
||||
private final String caseName;
|
||||
private String deviceID;
|
||||
private String dataSourceName;
|
||||
@@ -44,12 +45,13 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
||||
private final String value;
|
||||
private TskData.FileKnown known;
|
||||
private String comment;
|
||||
|
||||
|
||||
private AbstractFile originalAbstractFile = null;
|
||||
private CorrelationAttributeInstance originalCorrelationInstance = null;
|
||||
|
||||
|
||||
/**
|
||||
* Create a node from a central repo instance.
|
||||
*
|
||||
* @param instance The central repo instance
|
||||
* @param type The type of the instance
|
||||
* @param value The value of the instance
|
||||
@@ -63,15 +65,17 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
||||
this.value = value;
|
||||
known = instance.getKnownStatus();
|
||||
comment = instance.getComment();
|
||||
|
||||
|
||||
originalCorrelationInstance = instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a node from an abstract file.
|
||||
*
|
||||
* @param newFile The abstract file
|
||||
* @param autopsyCase The current case
|
||||
* @throws EamDbException
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws EamDbException {
|
||||
caseName = autopsyCase.getDisplayName();
|
||||
@@ -82,115 +86,129 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
||||
} catch (TskDataException | TskCoreException ex) {
|
||||
throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex);
|
||||
}
|
||||
|
||||
|
||||
filePath = newFile.getParentPath() + newFile.getName();
|
||||
typeStr = FILE_TYPE_STR;
|
||||
value = newFile.getMd5Hash();
|
||||
known = newFile.getKnown();
|
||||
comment = "";
|
||||
|
||||
|
||||
originalAbstractFile = newFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this node is a "file" type
|
||||
*
|
||||
* @return true if it is a file type
|
||||
*/
|
||||
boolean isFileType() {
|
||||
return FILE_TYPE_STR.equals(typeStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the known status for this node
|
||||
*
|
||||
* @param newKnownStatus The new known status
|
||||
*/
|
||||
void updateKnown(TskData.FileKnown newKnownStatus) {
|
||||
known = newKnownStatus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the comment for this node
|
||||
*
|
||||
* @param newComment The new comment
|
||||
*/
|
||||
void updateComment(String newComment) {
|
||||
comment = newComment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if this is a central repo node.
|
||||
* @return true if this node was created from a central repo instance, false otherwise
|
||||
*
|
||||
* @return true if this node was created from a central repo instance, false
|
||||
* otherwise
|
||||
*/
|
||||
boolean isCentralRepoNode() {
|
||||
return (originalCorrelationInstance != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the case name
|
||||
*
|
||||
* @return the case name
|
||||
*/
|
||||
String getCaseName() {
|
||||
return caseName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the device ID
|
||||
*
|
||||
* @return the device ID
|
||||
*/
|
||||
String getDeviceID() {
|
||||
return deviceID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the data source name
|
||||
*
|
||||
* @return the data source name
|
||||
*/
|
||||
String getDataSourceName() {
|
||||
return dataSourceName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the file path
|
||||
*
|
||||
* @return the file path
|
||||
*/
|
||||
String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the type (as a string)
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
String getType() {
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the value (MD5 hash for files)
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the known status
|
||||
*
|
||||
* @return the known status
|
||||
*/
|
||||
TskData.FileKnown getKnown() {
|
||||
return known;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the comment
|
||||
*
|
||||
* @return the comment
|
||||
*/
|
||||
String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the backing abstract file.
|
||||
* Should only be called if isCentralRepoNode() is false
|
||||
* Get the backing abstract file. Should only be called if
|
||||
* isCentralRepoNode() is false
|
||||
*
|
||||
* @return the original abstract file
|
||||
*/
|
||||
AbstractFile getAbstractFile() throws EamDbException {
|
||||
@@ -199,12 +217,14 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
||||
}
|
||||
return originalAbstractFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the backing CorrelationAttributeInstance.
|
||||
* Should only be called if isCentralRepoNode() is true
|
||||
* Get the backing CorrelationAttributeInstance. Should only be called if
|
||||
* isCentralRepoNode() is true
|
||||
*
|
||||
* @return the original CorrelationAttributeInstance
|
||||
* @throws EamDbException
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException {
|
||||
if (originalCorrelationInstance == null) {
|
||||
@@ -212,4 +232,33 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
||||
}
|
||||
return originalCorrelationInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string to append between elements when writing the node instance
|
||||
* data to a CSV
|
||||
*
|
||||
* @return the CSV_ITEM_SEPARATOR string
|
||||
*/
|
||||
static String getCsvItemSeparator() {
|
||||
return CSV_ITEM_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string representation of the node's data comma separated with a
|
||||
* line separator ending
|
||||
*
|
||||
* @return a comma separated string representation of the node's data
|
||||
*/
|
||||
String toCsvString() {
|
||||
StringBuilder line = new StringBuilder("\"");
|
||||
line.append(getCaseName()).append(CSV_ITEM_SEPARATOR)
|
||||
.append(getDataSourceName()).append(CSV_ITEM_SEPARATOR)
|
||||
.append(getType()).append(CSV_ITEM_SEPARATOR)
|
||||
.append(getValue()).append(CSV_ITEM_SEPARATOR)
|
||||
.append(getKnown().toString()).append(CSV_ITEM_SEPARATOR)
|
||||
.append(getFilePath()).append(CSV_ITEM_SEPARATOR)
|
||||
.append(getComment()).append('"')
|
||||
.append(System.getProperty("line.separator"));
|
||||
return line.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+29
-63
@@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
|
||||
|
||||
/**
|
||||
* Model for cells in the cases section of the other occurrences data content
|
||||
@@ -32,26 +33,16 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final List<CorrelationCaseWrapper> correlationCaseList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a table model for displaying case names
|
||||
*/
|
||||
OtherOccurrencesCasesTableModel() {
|
||||
// This constructor is intentionally empty.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return TableColumns.values().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preferred width that has been configured for this column.
|
||||
*
|
||||
* A value of 0 means that no preferred width has been defined for this
|
||||
* column.
|
||||
*
|
||||
* @param colIdx Column index
|
||||
*
|
||||
* @return preferred column width >= 0
|
||||
*/
|
||||
public int getColumnPreferredWidth(int colIdx) {
|
||||
return TableColumns.values()[colIdx].columnWidth();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,45 +50,43 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
|
||||
return correlationCaseList.size();
|
||||
}
|
||||
|
||||
@Messages({"OtherOccurrencesCasesTableModel.case=Case",})
|
||||
@Override
|
||||
public String getColumnName(int colIdx) {
|
||||
return TableColumns.values()[colIdx].columnName();
|
||||
return Bundle.OtherOccurrencesCasesTableModel_case();
|
||||
}
|
||||
|
||||
@Messages({"OtherOccurrencesCasesTableModel.noData=No Data."})
|
||||
@Override
|
||||
public Object getValueAt(int rowIdx, int colIdx) {
|
||||
if (0 == correlationCaseList.size()) {
|
||||
//if anything would prevent this from working we will consider it no data for the sake of simplicity
|
||||
if (correlationCaseList.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= correlationCaseList.size()
|
||||
|| correlationCaseList.get(rowIdx) == null
|
||||
|| correlationCaseList.get(rowIdx).getMessage() == null
|
||||
|| correlationCaseList.get(rowIdx).getMessage().isEmpty()) {
|
||||
return Bundle.OtherOccurrencesCasesTableModel_noData();
|
||||
}
|
||||
|
||||
CorrelationCaseWrapper caseWrapper = correlationCaseList.get(rowIdx);
|
||||
TableColumns columnId = TableColumns.values()[colIdx];
|
||||
return mapCorrelationCase(caseWrapper, columnId);
|
||||
return correlationCaseList.get(rowIdx).getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a column ID to the value in that cell for correlation case wrapper.
|
||||
* Get a correlation case for the selected index. Does not query the Central
|
||||
* Repository so CorrelationCase will be partial missing CR case ID and
|
||||
* other information that is stored in the CR.
|
||||
*
|
||||
* @param correlationCaseWrapper The correlation case wrapper
|
||||
* @param columnId The ID of the cell column.
|
||||
* @param rowIdx the row from the table model which corresponds to the case
|
||||
*
|
||||
* @return The value in the cell.
|
||||
* @return CorrelationCase for the table item specified or null if no
|
||||
* correlation could be found for any reason
|
||||
*/
|
||||
@Messages({"OtherOccurrencesCasesTableModel.noData=No Data."})
|
||||
private Object mapCorrelationCase(CorrelationCaseWrapper correlationCaseWrapper, TableColumns columnId) {
|
||||
String value = Bundle.OtherOccurrencesCasesTableModel_noData();
|
||||
|
||||
switch (columnId) {
|
||||
case CASE_NAME:
|
||||
value = correlationCaseWrapper.getMessage();
|
||||
break;
|
||||
default: //Use default "No data" value.
|
||||
break;
|
||||
CorrelationCase getCorrelationCase(int rowIdx) {
|
||||
//if anything would prevent this from working we will return null
|
||||
if (correlationCaseList.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= correlationCaseList.size()
|
||||
|| correlationCaseList.get(rowIdx) == null) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Object getCorrelationCase(int rowIdx) {
|
||||
return correlationCaseList.get(rowIdx).getCorrelationCase();
|
||||
}
|
||||
|
||||
@@ -107,7 +96,7 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one correlated instance object to the table
|
||||
* Add one correlation case wrapper object to the table
|
||||
*
|
||||
* @param newCorrelationCaseWrapper data to add to the table
|
||||
*/
|
||||
@@ -123,27 +112,4 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
|
||||
correlationCaseList.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
@Messages({"OtherOccurrencesCasesTableModel.case=Case",})
|
||||
enum TableColumns {
|
||||
// Ordering here determines displayed column order in Content Viewer.
|
||||
// If order is changed, update the CellRenderer to ensure correct row coloring.
|
||||
CASE_NAME(Bundle.OtherOccurrencesCasesTableModel_case(), 100);
|
||||
|
||||
private final String columnName;
|
||||
private final int columnWidth;
|
||||
|
||||
TableColumns(String columnName, int columnWidth) {
|
||||
this.columnName = columnName;
|
||||
this.columnWidth = columnWidth;
|
||||
}
|
||||
|
||||
public String columnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
public int columnWidth() {
|
||||
return columnWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 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.centralrepository.contentviewer;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Model for cells in the data sources section of the other occurrences data
|
||||
* content viewer
|
||||
*/
|
||||
final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Set<DataSourceColumnItem> dataSourceSet = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Create a table model for displaying data source names
|
||||
*/
|
||||
OtherOccurrencesDataSourcesTableModel() {
|
||||
// This constructor is intentionally empty.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return dataSourceSet.size();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name",
|
||||
"OtherOccurrencesDataSourcesTableModel.noData=No Data."})
|
||||
@Override
|
||||
public String getColumnName(int colIdx) {
|
||||
return Bundle.OtherOccurrencesDataSourcesTableModel_dataSourceName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIdx, int colIdx) {
|
||||
//if anything would prevent this from working we will consider it no data for the sake of simplicity
|
||||
if (dataSourceSet.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= dataSourceSet.size()
|
||||
|| !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) {
|
||||
return Bundle.OtherOccurrencesDataSourcesTableModel_noData();
|
||||
}
|
||||
return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDataSourceName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the device id of the data source shown at the specified row index
|
||||
*
|
||||
* @param rowIdx the row index of the data source you want the device id for
|
||||
*
|
||||
* @return the device id of the specified data source or an empty string if
|
||||
* a device id could not be retrieved
|
||||
*/
|
||||
String getDeviceIdForRow(int rowIdx) {
|
||||
//if anything would prevent this from working we will return an empty string
|
||||
if (dataSourceSet.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= dataSourceSet.size()
|
||||
|| !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) {
|
||||
return "";
|
||||
}
|
||||
return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDeviceId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the case name of the data source shown at the specified row index
|
||||
*
|
||||
* @param rowIdx the row index of the data source you want the case name for
|
||||
*
|
||||
* @return the case name of the specified data source or an empty string if
|
||||
* a case name could not be retrieved
|
||||
*/
|
||||
String getCaseNameForRow(int rowIdx) {
|
||||
//if anything would prevent this from working we will return an empty string
|
||||
if (dataSourceSet.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= dataSourceSet.size()
|
||||
|| !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) {
|
||||
return "";
|
||||
}
|
||||
return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getCaseName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getColumnClass(int colIdx) {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data source information to the table of unique data sources
|
||||
*
|
||||
* @param newNodeData data to add to the table
|
||||
*/
|
||||
void addNodeData(OtherOccurrenceNodeData newNodeData) {
|
||||
dataSourceSet.add(new DataSourceColumnItem((OtherOccurrenceNodeInstanceData) newNodeData));
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the node data table.
|
||||
*/
|
||||
void clearTable() {
|
||||
dataSourceSet.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private class for storing data source information in a way that
|
||||
* facilitates de-duping.
|
||||
*/
|
||||
private final class DataSourceColumnItem {
|
||||
|
||||
private final String caseName;
|
||||
private final String deviceId;
|
||||
private final String dataSourceName;
|
||||
|
||||
/**
|
||||
* Create a DataSourceColumnItem given an
|
||||
* OtherOccurrenceNodeInstanceData object
|
||||
*
|
||||
* @param nodeData the OtherOccurrenceNodeInstanceData which contains
|
||||
* the data source information
|
||||
*/
|
||||
private DataSourceColumnItem(OtherOccurrenceNodeInstanceData nodeData) {
|
||||
this(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DataSourceColumnItem given a case name, device id, and data
|
||||
* source name
|
||||
*
|
||||
* @param caseName the name of the case the data source exists in
|
||||
* @param deviceId the name of the device id for the data source
|
||||
* @param dataSourceName the name of the data source
|
||||
*/
|
||||
private DataSourceColumnItem(String caseName, String deviceId, String dataSourceName) {
|
||||
this.caseName = caseName;
|
||||
this.deviceId = deviceId;
|
||||
this.dataSourceName = dataSourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the device id
|
||||
*
|
||||
* @return the data source's device id
|
||||
*/
|
||||
private String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data source name
|
||||
*
|
||||
* @return the data source's name
|
||||
*/
|
||||
private String getDataSourceName() {
|
||||
return dataSourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the case the data source exists in
|
||||
*
|
||||
* @return the name of the case the data source is in
|
||||
*/
|
||||
private String getCaseName() {
|
||||
return caseName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof DataSourceColumnItem
|
||||
&& caseName.equals(((DataSourceColumnItem) other).getCaseName())
|
||||
&& dataSourceName.equals(((DataSourceColumnItem) other).getDataSourceName())
|
||||
&& deviceId.equals(((DataSourceColumnItem) other).getDeviceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(caseName, deviceId, dataSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2015-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.centralrepository.contentviewer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Renderer for cells in the files section of the other occurrences data content viewer
|
||||
*/
|
||||
public class OtherOccurrencesFilesTableCellRenderer implements TableCellRenderer {
|
||||
|
||||
public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer();
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(
|
||||
JTable table,
|
||||
Object value,
|
||||
boolean isSelected,
|
||||
boolean hasFocus,
|
||||
int row,
|
||||
int column) {
|
||||
Component renderer = DEFAULT_RENDERER.getTableCellRendererComponent(
|
||||
table, value, isSelected, hasFocus, row, column);
|
||||
((JComponent) renderer).setOpaque(true);
|
||||
Color foreground, background;
|
||||
if (isSelected) {
|
||||
foreground = Color.WHITE;
|
||||
background = new Color(51,153,255);
|
||||
} else {
|
||||
String known_status = (String) table.getModel().getValueAt(table.convertRowIndexToModel(row),
|
||||
table.getColumn(OtherOccurrencesFilesTableModel.TableColumns.KNOWN.columnName()).getModelIndex());
|
||||
if (known_status.equals(TskData.FileKnown.BAD.getName())) {
|
||||
foreground = Color.WHITE;
|
||||
background = Color.RED;
|
||||
} else if (known_status.equals(TskData.FileKnown.UNKNOWN.getName())) {
|
||||
foreground = Color.BLACK;
|
||||
background = Color.WHITE;
|
||||
} else {
|
||||
foreground = Color.BLACK;
|
||||
background = Color.WHITE;
|
||||
}
|
||||
}
|
||||
renderer.setForeground(foreground);
|
||||
renderer.setBackground(background);
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
+48
-110
@@ -19,150 +19,76 @@
|
||||
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
/**
|
||||
* Model for cells in the files section of the other occurrences data content viewer
|
||||
* Model for cells in the files section of the other occurrences data content
|
||||
* viewer
|
||||
*/
|
||||
public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final List<String> nodeKeys = new ArrayList<>();
|
||||
private final Map<String, List<OtherOccurrenceNodeData>> nodeMap = new HashMap<>();
|
||||
|
||||
@Messages({"OtherOccurrencesFilesTableModel.device=Device",
|
||||
"OtherOccurrencesFilesTableModel.dataSource=Data Source",
|
||||
"OtherOccurrencesFilesTableModel.path=Path",
|
||||
"OtherOccurrencesFilesTableModel.attribute=Matched Attribute",
|
||||
"OtherOccurrencesFilesTableModel.value=Attribute Value",
|
||||
"OtherOccurrencesFilesTableModel.known=Known",
|
||||
"OtherOccurrencesFilesTableModel.comment=Comment",
|
||||
"OtherOccurrencesFilesTableModel.noData=No Data.",})
|
||||
enum TableColumns {
|
||||
// Ordering here determines displayed column order in Content Viewer.
|
||||
// If order is changed, update the CellRenderer to ensure correct row coloring.
|
||||
ATTRIBUTE(Bundle.OtherOccurrencesFilesTableModel_attribute(), 75),
|
||||
VALUE(Bundle.OtherOccurrencesFilesTableModel_value(), 190),
|
||||
KNOWN(Bundle.OtherOccurrencesFilesTableModel_known(), 25),
|
||||
FILE_PATH(Bundle.OtherOccurrencesFilesTableModel_path(), 470),
|
||||
COMMENT(Bundle.OtherOccurrencesFilesTableModel_comment(), 190);
|
||||
|
||||
private final String columnName;
|
||||
private final int columnWidth;
|
||||
|
||||
TableColumns(String columnName, int columnWidth) {
|
||||
this.columnName = columnName;
|
||||
this.columnWidth = columnWidth;
|
||||
}
|
||||
|
||||
public String columnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
public int columnWidth() {
|
||||
return columnWidth;
|
||||
}
|
||||
};
|
||||
|
||||
private final List<OtherOccurrenceNodeData> nodeDataList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a table model for displaying file names
|
||||
*/
|
||||
OtherOccurrencesFilesTableModel() {
|
||||
|
||||
// This constructor is intentionally empty.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return TableColumns.values().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preferred width that has been configured for this column.
|
||||
*
|
||||
* A value of 0 means that no preferred width has been defined for this
|
||||
* column.
|
||||
*
|
||||
* @param colIdx Column index
|
||||
*
|
||||
* @return preferred column width >= 0
|
||||
*/
|
||||
public int getColumnPreferredWidth(int colIdx) {
|
||||
return TableColumns.values()[colIdx].columnWidth();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return nodeDataList.size();
|
||||
return nodeKeys.size();
|
||||
}
|
||||
|
||||
@Messages({"OtherOccurrencesFilesTableModel.fileName=File Name",
|
||||
"OtherOccurrencesFilesTableModel.noData=No Data."})
|
||||
@Override
|
||||
public String getColumnName(int colIdx) {
|
||||
return TableColumns.values()[colIdx].columnName();
|
||||
return Bundle.OtherOccurrencesFilesTableModel_fileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIdx, int colIdx) {
|
||||
if (0 == nodeDataList.size()) {
|
||||
//if anything would prevent this from working we will consider it no data for the sake of simplicity
|
||||
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
|
||||
|| nodeMap.get(nodeKeys.get(rowIdx)) == null
|
||||
|| nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) {
|
||||
return Bundle.OtherOccurrencesFilesTableModel_noData();
|
||||
}
|
||||
|
||||
OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx);
|
||||
TableColumns columnId = TableColumns.values()[colIdx];
|
||||
if (nodeData instanceof OtherOccurrenceNodeMessageData) {
|
||||
return mapNodeMessageData((OtherOccurrenceNodeMessageData) nodeData, columnId);
|
||||
}
|
||||
return mapNodeInstanceData((OtherOccurrenceNodeInstanceData) nodeData, columnId);
|
||||
return FilenameUtils.getName(((OtherOccurrenceNodeInstanceData) nodeMap.get(nodeKeys.get(rowIdx)).get(0)).getFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a column ID to the value in that cell for node message data.
|
||||
* Get a list of OtherOccurrenceNodeData that exist for the file which
|
||||
* corresponds to the Index
|
||||
*
|
||||
* @param nodeData The node message data.
|
||||
* @param columnId The ID of the cell column.
|
||||
* @param rowIdx the index of the file to get data for
|
||||
*
|
||||
* @return The value in the cell.
|
||||
* @return a list of OtherOccurrenceNodeData for the specified index or an
|
||||
* empty list if no data was found
|
||||
*/
|
||||
private Object mapNodeMessageData(OtherOccurrenceNodeMessageData nodeData, TableColumns columnId) {
|
||||
if (columnId == TableColumns.ATTRIBUTE) {
|
||||
return nodeData.getDisplayMessage();
|
||||
List<OtherOccurrenceNodeData> getListOfNodesForFile(int rowIdx) {
|
||||
//if anything would prevent this from working return an empty list
|
||||
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|
||||
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
|
||||
|| nodeMap.get(nodeKeys.get(rowIdx)) == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a column ID to the value in that cell for node instance data.
|
||||
*
|
||||
* @param nodeData The node instance data.
|
||||
* @param columnId The ID of the cell column.
|
||||
*
|
||||
* @return The value in the cell.
|
||||
*/
|
||||
private Object mapNodeInstanceData(OtherOccurrenceNodeInstanceData nodeData, TableColumns columnId) {
|
||||
String value = Bundle.OtherOccurrencesFilesTableModel_noData();
|
||||
|
||||
switch (columnId) {
|
||||
case FILE_PATH:
|
||||
value = nodeData.getFilePath();
|
||||
break;
|
||||
case ATTRIBUTE:
|
||||
value = nodeData.getType();
|
||||
break;
|
||||
case VALUE:
|
||||
value = nodeData.getValue();
|
||||
break;
|
||||
case KNOWN:
|
||||
value = nodeData.getKnown().getName();
|
||||
break;
|
||||
case COMMENT:
|
||||
value = nodeData.getComment();
|
||||
break;
|
||||
default: //Use default "No data" value.
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Object getRow(int rowIdx) {
|
||||
return nodeDataList.get(rowIdx);
|
||||
return nodeMap.get(nodeKeys.get(rowIdx));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -176,15 +102,27 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|
||||
* @param newNodeData data to add to the table
|
||||
*/
|
||||
void addNodeData(OtherOccurrenceNodeData newNodeData) {
|
||||
nodeDataList.add(newNodeData);
|
||||
String newNodeKey = createNodeKey((OtherOccurrenceNodeInstanceData) newNodeData);//FilenameUtils.getName(((OtherOccurrenceNodeInstanceData)newNodeData).getFilePath());
|
||||
List<OtherOccurrenceNodeData> nodeList = nodeMap.get(newNodeKey);
|
||||
if (nodeList == null) {
|
||||
nodeKeys.add(newNodeKey);
|
||||
nodeList = new ArrayList<>();
|
||||
}
|
||||
nodeList.add(newNodeData);
|
||||
nodeMap.put(newNodeKey, nodeList);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) {
|
||||
return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the node data table.
|
||||
*/
|
||||
void clearTable() {
|
||||
nodeDataList.clear();
|
||||
nodeKeys.clear();
|
||||
nodeMap.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ public class CommandLineIngestManager {
|
||||
// read options panel configuration
|
||||
String rootOutputDir = UserPreferences.getCommandLineModeResultsFolder();
|
||||
LOGGER.log(Level.INFO, "Output directory = {0}", rootOutputDir); //NON-NLS
|
||||
System.out.println("Output directoryh = " + rootOutputDir);
|
||||
System.out.println("Output directory = " + rootOutputDir);
|
||||
|
||||
if (rootOutputDir.isEmpty()) {
|
||||
LOGGER.log(Level.SEVERE, "Output directory not specified, please configure Command Line Options Panel (in Tools -> Options)");
|
||||
@@ -264,6 +264,7 @@ public class CommandLineIngestManager {
|
||||
* Passes the data source for the current job through a data source
|
||||
* processor that adds it to the case database.
|
||||
*
|
||||
* @param caseForJob The case
|
||||
* @param dataSource The data source.
|
||||
*
|
||||
* @throws
|
||||
|
||||
@@ -56,22 +56,22 @@ public class CommandLineOptionProcessor extends OptionProcessor {
|
||||
@Override
|
||||
protected void process(Env env, Map<Option, String[]> values) throws CommandException {
|
||||
logger.log(Level.INFO, "Processing Autopsy command line options"); //NON-NLS
|
||||
System.out.println("Processing Autopsy command line options using CommandLineOptionProcessor");
|
||||
System.out.println("Processing Autopsy command line options");
|
||||
if (values.containsKey(pathToDataSourceOption) && values.containsKey(caseNameOption) && values.containsKey(runFromCommandLineOption)) {
|
||||
// parse input parameters
|
||||
String inputPath;
|
||||
String inputCaseName;
|
||||
String modeString;
|
||||
if (values.size() < 3) {
|
||||
logger.log(Level.SEVERE, "Insufficient number of input arguments. Exiting");
|
||||
System.out.println("Insufficient number of input arguments. Exiting");
|
||||
logger.log(Level.SEVERE, "Insufficient number of input arguments to run command line ingest");
|
||||
System.out.println("Insufficient number of input arguments to run command line ingest");
|
||||
this.runFromCommandLine = false;
|
||||
return;
|
||||
} else {
|
||||
String[] argDirs = values.get(pathToDataSourceOption);
|
||||
if (argDirs.length < 1) {
|
||||
logger.log(Level.SEVERE, "Missing argument 'inputPath'. Exiting");
|
||||
System.out.println("Missing argument 'inputPath'. Exiting");
|
||||
logger.log(Level.SEVERE, "Missing argument 'inputPath'");
|
||||
System.out.println("Missing argument 'inputPath'");
|
||||
this.runFromCommandLine = false;
|
||||
return;
|
||||
}
|
||||
@@ -79,8 +79,8 @@ public class CommandLineOptionProcessor extends OptionProcessor {
|
||||
|
||||
argDirs = values.get(caseNameOption);
|
||||
if (argDirs.length < 1) {
|
||||
logger.log(Level.SEVERE, "Missing argument 'caseName'. Exiting");
|
||||
System.out.println("Missing argument 'caseName'. Exiting");
|
||||
logger.log(Level.SEVERE, "Missing argument 'caseName'");
|
||||
System.out.println("Missing argument 'caseName'");
|
||||
this.runFromCommandLine = false;
|
||||
return;
|
||||
}
|
||||
@@ -88,8 +88,8 @@ public class CommandLineOptionProcessor extends OptionProcessor {
|
||||
|
||||
argDirs = values.get(runFromCommandLineOption);
|
||||
if (argDirs.length < 1) {
|
||||
logger.log(Level.SEVERE, "Missing argument 'runFromCommandLine'. Exiting");
|
||||
System.out.println("Missing argument 'runFromCommandLine'. Exiting");
|
||||
logger.log(Level.SEVERE, "Missing argument 'runFromCommandLine'");
|
||||
System.out.println("Missing argument 'runFromCommandLine'");
|
||||
this.runFromCommandLine = false;
|
||||
return;
|
||||
}
|
||||
@@ -111,15 +111,15 @@ public class CommandLineOptionProcessor extends OptionProcessor {
|
||||
|
||||
// verify inputs
|
||||
if (inputPath == null || inputPath.isEmpty() || !(new File(inputPath).exists())) {
|
||||
logger.log(Level.SEVERE, "Input file {0} doesn''t exist. Exiting", inputPath);
|
||||
System.out.println("Input file " + inputPath + " doesn't exist. Exiting");
|
||||
logger.log(Level.SEVERE, "Input file {0} doesn''t exist", inputPath);
|
||||
System.out.println("Input file " + inputPath + " doesn't exist");
|
||||
this.runFromCommandLine = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputCaseName == null || inputCaseName.isEmpty()) {
|
||||
logger.log(Level.SEVERE, "Case name argument is empty. Exiting");
|
||||
System.out.println("Case name argument is empty. Exiting");
|
||||
logger.log(Level.SEVERE, "Case name argument is empty");
|
||||
System.out.println("Case name argument is empty");
|
||||
this.runFromCommandLine = false;
|
||||
return;
|
||||
}
|
||||
@@ -134,8 +134,8 @@ public class CommandLineOptionProcessor extends OptionProcessor {
|
||||
System.out.println("Case name = " + this.baseCaseName);
|
||||
System.out.println("runFromCommandLine = " + this.runFromCommandLine);
|
||||
} else {
|
||||
System.out.println("Missing input arguments for CommandLineOptionProcessor. Exiting");
|
||||
logger.log(Level.SEVERE, "Missing input arguments. Exiting");
|
||||
System.out.println("Missing input arguments to run command line ingest");
|
||||
logger.log(Level.SEVERE, "Missing input arguments to run command line ingest");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ public class CommonAttributeValueNode extends DisplayableItemNode {
|
||||
* Create a Match node whose children will all have this object in common.
|
||||
*
|
||||
* @param data the common feature, and the children
|
||||
* @param type the data type
|
||||
*/
|
||||
public CommonAttributeValueNode(CommonAttributeValue data, CorrelationAttributeInstance.Type type) {
|
||||
super(Children.create(
|
||||
|
||||
@@ -52,8 +52,9 @@ public final class InstanceCountNode extends DisplayableItemNode {
|
||||
* Create a node with the given number of instances, and the given selection
|
||||
* of metadata.
|
||||
*
|
||||
* @param instanceCount
|
||||
* @param attributeValues
|
||||
* @param instanceCount the number of instances
|
||||
* @param attributeValues the attribute list
|
||||
* @param type the data type
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"InstanceCountNode.displayName=Exists in %s data sources (%s)"
|
||||
|
||||
@@ -59,6 +59,7 @@ public class CommSnapShotReportWriter extends UiSnapShotReportWriter {
|
||||
* @param reportName The name of the report.
|
||||
* @param generationDate The generation Date of the report.
|
||||
* @param snapshot A snapshot of the view to include in the report.
|
||||
* @param filter The communications filter
|
||||
*/
|
||||
public CommSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate, BufferedImage snapshot, CommunicationsFilter filter) {
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ FXVideoPanel.progress.bufferingFile=Buffering {0}
|
||||
FXVideoPanel.progressLabel.buffering=Buffering...
|
||||
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
||||
GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
|
||||
GstVideoPanel.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
||||
GstVideoPanel.exception.problemFile.msg=Cannot capture frames from this file ({0}).
|
||||
GstVideoPanel.exception.problemPlay.msg=Problem with video file; problem when attempting to play while obtaining duration.
|
||||
@@ -84,3 +84,8 @@ MediaViewImagePanel.zoomTextField.text=
|
||||
MediaViewImagePanel.rotationTextField.text=
|
||||
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
||||
HtmlPanel.showImagesToggleButton.text=Show Images
|
||||
MediaPlayerPanel.audioSlider.toolTipText=
|
||||
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
|
||||
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
|
||||
MediaPlayerPanel.playButton.text=\u25ba
|
||||
MediaPlayerPanel.infoLabel.text=No Errors
|
||||
|
||||
@@ -19,8 +19,12 @@ FXVideoPanel.progress.bufferingFile=Buffering {0}
|
||||
FXVideoPanel.progressLabel.buffering=Buffering...
|
||||
FXVideoPanel.media.unsupportedFormat=Unsupported Format.
|
||||
GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
|
||||
GstVideoPanel.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||
GstVideoPanel.noOpenCase.errMsg=No open case available.
|
||||
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
|
||||
HtmlPanel_showImagesToggleButton_hide=Hide Images
|
||||
HtmlPanel_showImagesToggleButton_show=Show Images
|
||||
HtmlViewer_file_error=This file is missing or unreadable.
|
||||
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
|
||||
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
|
||||
GstVideoPanel.exception.problemFile.msg=Cannot capture frames from this file ({0}).
|
||||
GstVideoPanel.exception.problemPlay.msg=Problem with video file; problem when attempting to play while obtaining duration.
|
||||
@@ -32,13 +36,12 @@ GstVideoPanel.progress.buffering=Buffering...
|
||||
GstVideoPanel.progressLabel.bufferingErr=Error buffering file
|
||||
GstVideoPanel.progress.infoLabel.updateErr=Error updating video progress: {0}
|
||||
GstVideoPanel.ExtractMedia.progress.buffering=Buffering {0}
|
||||
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
|
||||
HtmlPanel_showImagesToggleButton_hide=Hide Images
|
||||
HtmlPanel_showImagesToggleButton_show=Show Images
|
||||
HtmlViewer_file_error=This file is missing or unreadable.
|
||||
MediaFileViewer.AccessibleContext.accessibleDescription=
|
||||
MediaFileViewer.title=Media
|
||||
MediaFileViewer.toolTip=Displays supported multimedia files (images, videos, audio)
|
||||
MediaPlayerPanel.noSupport=File not supported.
|
||||
MediaPlayerPanel.timeFormat=%02d:%02d:%02d
|
||||
MediaPlayerPanel.unknownTime=Unknown
|
||||
MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory.
|
||||
MediaViewImagePanel.errorLabel.text=Could not load file into Media View.
|
||||
MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E
|
||||
@@ -143,6 +146,11 @@ MediaViewImagePanel.zoomTextField.text=
|
||||
MediaViewImagePanel.rotationTextField.text=
|
||||
MediaViewImagePanel.rotateLeftButton.toolTipText=
|
||||
HtmlPanel.showImagesToggleButton.text=Show Images
|
||||
MediaPlayerPanel.audioSlider.toolTipText=
|
||||
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
|
||||
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
|
||||
MediaPlayerPanel.playButton.text=\u25ba
|
||||
MediaPlayerPanel.infoLabel.text=No Errors
|
||||
# {0} - tableName
|
||||
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
|
||||
# {0} - tableName
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 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.contentviewers;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelFormat;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.freedesktop.gstreamer.Buffer;
|
||||
import org.freedesktop.gstreamer.Caps;
|
||||
import org.freedesktop.gstreamer.FlowReturn;
|
||||
import org.freedesktop.gstreamer.Sample;
|
||||
import org.freedesktop.gstreamer.Structure;
|
||||
import org.freedesktop.gstreamer.elements.AppSink;
|
||||
|
||||
/**
|
||||
* This is a video renderer for GStreamer.
|
||||
*/
|
||||
final class GstVideoRendererPanel extends JFXPanel {
|
||||
|
||||
private static final String CAP_MIME_TYPE = "video/x-raw";
|
||||
private static final String CAP_BYTE_ORDER = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? "format=BGRx" : "format=xRGB");
|
||||
private static final int PROP_MAX_BUFFERS = 5000;
|
||||
private AppSink videoSink;
|
||||
private ImageView fxImageView;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
GstVideoRendererPanel() {
|
||||
initImageView();
|
||||
initVideoSink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the ImageView to show the current frame.
|
||||
*/
|
||||
private void initImageView() {
|
||||
fxImageView = new ImageView(); // Will hold the current video frame.
|
||||
BorderPane borderpane = new BorderPane(fxImageView); // Center and size ImageView.
|
||||
Scene scene = new Scene(borderpane); // Root of the JavaFX tree.
|
||||
setScene(scene);
|
||||
|
||||
// Bind size of image to that of scene, while keeping proportions
|
||||
fxImageView.fitWidthProperty().bind(scene.widthProperty());
|
||||
fxImageView.fitHeightProperty().bind(scene.heightProperty());
|
||||
fxImageView.setPreserveRatio(true);
|
||||
fxImageView.setSmooth(true);
|
||||
fxImageView.setCache(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the video sink.
|
||||
*/
|
||||
private void initVideoSink() {
|
||||
videoSink = new AppSink("GstVideoComponent");
|
||||
videoSink.set("emit-signals", true);
|
||||
AppSinkListener gstListener = new AppSinkListener();
|
||||
videoSink.connect(gstListener);
|
||||
videoSink.setCaps(new Caps(
|
||||
String.format("%s, %s", CAP_MIME_TYPE, CAP_BYTE_ORDER)));
|
||||
videoSink.set("max-buffers", PROP_MAX_BUFFERS);
|
||||
videoSink.set("drop", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video sink.
|
||||
*
|
||||
* @return The video sink.
|
||||
*/
|
||||
AppSink getVideoSink() {
|
||||
return videoSink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for NEW_SAMPLE events to update the ImageView with the newest
|
||||
* video frame.
|
||||
*/
|
||||
class AppSinkListener implements AppSink.NEW_SAMPLE {
|
||||
|
||||
private Image videoFrame;
|
||||
private int lastWidth = 0;
|
||||
private int lastHeight = 0;
|
||||
private byte[] byteArray;
|
||||
|
||||
@Override
|
||||
public FlowReturn newSample(AppSink appSink) {
|
||||
Sample sample = appSink.pullSample();
|
||||
Buffer buffer = sample.getBuffer();
|
||||
ByteBuffer byteBuffer = buffer.map(false);
|
||||
if (byteBuffer != null) {
|
||||
Structure capsStruct = sample.getCaps().getStructure(0);
|
||||
int width = capsStruct.getInteger("width");
|
||||
int height = capsStruct.getInteger("height");
|
||||
if (width != lastWidth || height != lastHeight) {
|
||||
lastWidth = width;
|
||||
lastHeight = height;
|
||||
byteArray = new byte[width * height * 4];
|
||||
}
|
||||
byteBuffer.get(byteArray);
|
||||
videoFrame = convertBytesToImage(byteArray, width, height);
|
||||
Platform.runLater(() -> {
|
||||
fxImageView.setImage(videoFrame);
|
||||
});
|
||||
buffer.unmap();
|
||||
}
|
||||
sample.dispose();
|
||||
|
||||
return FlowReturn.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an image from a byte array of pixels.
|
||||
*
|
||||
* @param pixels The byte array of pixels.
|
||||
* @param width The width of the image.
|
||||
* @param height The height of the image.
|
||||
*
|
||||
* @return The image.
|
||||
*/
|
||||
private Image convertBytesToImage(byte[] pixels, int width, int height) {
|
||||
WritableImage image = new WritableImage(width, height);
|
||||
PixelWriter pixelWriter = image.getPixelWriter();
|
||||
pixelWriter.setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), pixels, 0, width * 4);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 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.contentviewers;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelFormat;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.freedesktop.gstreamer.Buffer;
|
||||
import org.freedesktop.gstreamer.Caps;
|
||||
import org.freedesktop.gstreamer.FlowReturn;
|
||||
import org.freedesktop.gstreamer.Sample;
|
||||
import org.freedesktop.gstreamer.Structure;
|
||||
import org.freedesktop.gstreamer.elements.AppSink;
|
||||
|
||||
/**
|
||||
* This is a JavaFX Video renderer for GStreamer
|
||||
*/
|
||||
final class JavaFxAppSink extends AppSink {
|
||||
|
||||
private static final String CAP_MIME_TYPE = "video/x-raw";
|
||||
private static final String CAP_BYTE_ORDER = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? "format=BGRx" : "format=xRGB");
|
||||
private static final int PROP_MAX_BUFFERS = 5000;
|
||||
|
||||
private final JavaFxFrameUpdater updater;
|
||||
|
||||
/**
|
||||
* Creates a new AppSink that hooks an ImageView into a JFXPanel. This AppSink
|
||||
* comes prepackaged with an AppSink listener to accomplish the previous statement.
|
||||
*
|
||||
* @param name AppSink internal name
|
||||
* @param target JFXPanel to display video playback in
|
||||
*/
|
||||
public JavaFxAppSink(String name, JFXPanel target) {
|
||||
super(name);
|
||||
set("emit-signals", true);
|
||||
updater = new JavaFxFrameUpdater(target);
|
||||
connect((AppSink.NEW_SAMPLE) updater);
|
||||
connect((AppSink.NEW_PREROLL) updater);
|
||||
setCaps(new Caps(
|
||||
String.format("%s, %s", CAP_MIME_TYPE, CAP_BYTE_ORDER)));
|
||||
set("max-buffers", PROP_MAX_BUFFERS);
|
||||
set("drop", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current frame in the JFXPanel
|
||||
*/
|
||||
public void clear() {
|
||||
disconnect((AppSink.NEW_SAMPLE) updater);
|
||||
disconnect((AppSink.NEW_PREROLL) updater);
|
||||
updater.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for keeping the ImageView that is hooked into the JFXPanel up-to-date
|
||||
* with the most current or available frame from GStreamer.
|
||||
*/
|
||||
static class JavaFxFrameUpdater implements AppSink.NEW_SAMPLE, AppSink.NEW_PREROLL {
|
||||
private final ImageView fxImageView;
|
||||
|
||||
public JavaFxFrameUpdater(JFXPanel target) {
|
||||
//We should probably pass an ImageView instead of a JFXPanel to make
|
||||
//it more reuseable
|
||||
fxImageView = new ImageView(); // Will hold the current video frame.
|
||||
BorderPane borderpane = new BorderPane(fxImageView); // Center and size ImageView.
|
||||
Scene scene = new Scene(borderpane); // Root of the JavaFX tree.
|
||||
target.setScene(scene);
|
||||
|
||||
// Bind size of image to that of scene, while keeping proportions
|
||||
fxImageView.fitWidthProperty().bind(scene.widthProperty());
|
||||
fxImageView.fitHeightProperty().bind(scene.heightProperty());
|
||||
fxImageView.setPreserveRatio(true);
|
||||
fxImageView.setSmooth(true);
|
||||
fxImageView.setCache(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ImageView when a brand new frame is in the pipeline.
|
||||
*
|
||||
* @param appSink Pipeline containing the new frame
|
||||
* @return Result of update
|
||||
*/
|
||||
@Override
|
||||
public FlowReturn newSample(AppSink appSink) {
|
||||
return setSample(appSink.pullSample());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ImageView to the input sample. Sample here is synonymous with
|
||||
* frame.
|
||||
*
|
||||
* @param input Frame
|
||||
* @return Result of update
|
||||
*/
|
||||
public FlowReturn setSample(Sample input) {
|
||||
Buffer buffer = input.getBuffer();
|
||||
ByteBuffer byteBuffer = buffer.map(false);
|
||||
if (byteBuffer != null) {
|
||||
Structure capsStruct = input.getCaps().getStructure(0);
|
||||
int width = capsStruct.getInteger("width");
|
||||
int height = capsStruct.getInteger("height");
|
||||
byte[] byteArray = new byte[width * height * 4];
|
||||
byteBuffer.get(byteArray);
|
||||
Image videoFrame = convertBytesToImage(byteArray, width, height);
|
||||
fxImageView.setImage(videoFrame);
|
||||
buffer.unmap();
|
||||
}
|
||||
input.dispose();
|
||||
|
||||
//Keep frames rolling
|
||||
return FlowReturn.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ImageView with the next frame in the pipeline, without
|
||||
* removing it. This function is invoked when Gstreamer is not in a
|
||||
* PLAYING state, but we can peek at what's to come.
|
||||
*
|
||||
* It's essential for displaying the initial frame when a video is first
|
||||
* selected.
|
||||
*
|
||||
* @param sink Pipeline containing video data
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public FlowReturn newPreroll(AppSink sink) {
|
||||
//Grab the next frame without removing it from the pipeline
|
||||
Sample sample = sink.pullPreroll();
|
||||
return setSample(sample);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an image from a byte array of pixels.
|
||||
*
|
||||
* @param pixels The byte array of pixels.
|
||||
* @param width The width of the image.
|
||||
* @param height The height of the image.
|
||||
*
|
||||
* @return The image.
|
||||
*/
|
||||
private Image convertBytesToImage(byte[] pixels, int width, int height) {
|
||||
WritableImage image = new WritableImage(width, height);
|
||||
PixelWriter pixelWriter = image.getPixelWriter();
|
||||
pixelWriter.setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), pixels, 0, width * 4);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current frame from the display
|
||||
*/
|
||||
void clear() {
|
||||
fxImageView.setImage(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,13 @@ package org.sleuthkit.autopsy.contentviewers;
|
||||
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.freedesktop.gstreamer.GstException;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
@@ -36,8 +38,7 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName());
|
||||
private AbstractFile lastFile;
|
||||
//UI
|
||||
private final MediaPlayerPanel mediaPlayerPanel;
|
||||
private final boolean mediaPlayerPanelInited;
|
||||
private MediaPlayerPanel mediaPlayerPanel;
|
||||
private final MediaViewImagePanel imagePanel;
|
||||
private final boolean imagePanelInited;
|
||||
|
||||
@@ -51,10 +52,14 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
initComponents();
|
||||
|
||||
// get the right panel for our platform
|
||||
mediaPlayerPanel = new MediaPlayerPanel();
|
||||
mediaPlayerPanelInited = mediaPlayerPanel.isInited();
|
||||
|
||||
try {
|
||||
mediaPlayerPanel = new MediaPlayerPanel();
|
||||
} catch (GstException | UnsatisfiedLinkError ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(this.getClass(), "MediaFileViewer.initGst.gstException.msg"),
|
||||
ex.getMessage());
|
||||
}
|
||||
imagePanel = new MediaViewImagePanel();
|
||||
imagePanelInited = imagePanel.isInited();
|
||||
|
||||
@@ -64,7 +69,10 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
private void customizeComponents() {
|
||||
add(imagePanel, IMAGE_VIEWER_LAYER);
|
||||
add(mediaPlayerPanel, MEDIA_PLAYER_LAYER);
|
||||
|
||||
if(mediaPlayerPanel != null) {
|
||||
add(mediaPlayerPanel, MEDIA_PLAYER_LAYER);
|
||||
}
|
||||
|
||||
showImagePanel();
|
||||
}
|
||||
@@ -86,30 +94,31 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
/**
|
||||
* Returns a list of mimetypes supported by this viewer
|
||||
*
|
||||
*
|
||||
* @return list of supported mimetypes
|
||||
*/
|
||||
@Override
|
||||
public List<String> getSupportedMIMETypes() {
|
||||
|
||||
|
||||
List<String> mimeTypes = new ArrayList<>();
|
||||
|
||||
|
||||
mimeTypes.addAll(this.imagePanel.getSupportedMimeTypes());
|
||||
mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes());
|
||||
if(mediaPlayerPanel != null) {
|
||||
mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes());
|
||||
}
|
||||
|
||||
return mimeTypes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set up the view to display the given file.
|
||||
*
|
||||
*
|
||||
* @param file file to display
|
||||
*/
|
||||
@Override
|
||||
public void setFile(AbstractFile file) {
|
||||
try {
|
||||
|
||||
|
||||
if (file == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
@@ -120,14 +129,11 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
}
|
||||
|
||||
lastFile = file;
|
||||
|
||||
final Dimension dims = MediaFileViewer.this.getSize();
|
||||
//logger.info("setting node on media viewer"); //NON-NLS
|
||||
if (mediaPlayerPanelInited && mediaPlayerPanel.isSupported(file)) {
|
||||
mediaPlayerPanel.loadFile(file, dims);
|
||||
if (mediaPlayerPanel != null && mediaPlayerPanel.isSupported(file)) {
|
||||
mediaPlayerPanel.loadFile(file);
|
||||
this.showVideoPanel();
|
||||
} else if (imagePanelInited && imagePanel.isSupported(file)) {
|
||||
imagePanel.showImageFx(file, dims);
|
||||
imagePanel.showImageFx(file);
|
||||
this.showImagePanel();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -142,7 +148,7 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
CardLayout layout = (CardLayout) this.getLayout();
|
||||
layout.show(this, MEDIA_PLAYER_LAYER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the image panel.
|
||||
*/
|
||||
@@ -158,7 +164,9 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
|
||||
@Override
|
||||
public void resetComponent() {
|
||||
mediaPlayerPanel.reset();
|
||||
if (mediaPlayerPanel != null) {
|
||||
mediaPlayerPanel.reset();
|
||||
}
|
||||
imagePanel.reset();
|
||||
lastFile = null;
|
||||
}
|
||||
|
||||
Regular → Executable
+84
-34
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
@@ -16,8 +16,8 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="controlPanel" max="32767" attributes="0"/>
|
||||
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
@@ -41,7 +41,7 @@
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="231" max="32767" attributes="0"/>
|
||||
<EmptySpace min="0" pref="259" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
@@ -51,66 +51,116 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="6" pref="6" max="-2" attributes="0"/>
|
||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="playButton" min="-2" pref="64" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="progressSlider" pref="680" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="progressSlider" pref="265" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="infoLabel" max="32767" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="VolumeIcon" min="-2" pref="64" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
|
||||
<Component id="audioSlider" min="-2" pref="229" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="progressSlider" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="progressLabel" alignment="0" min="-2" pref="29" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="progressLabel" max="32767" attributes="0"/>
|
||||
<Component id="progressSlider" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="playButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="audioSlider" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="VolumeIcon" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
|
||||
<Component id="infoLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="pauseButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.pauseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pauseButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSlider" name="progressSlider">
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="progressLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<Property name="value" type="int" value="0"/>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="Default Cursor"/>
|
||||
</Property>
|
||||
<Property name="doubleBuffered" type="boolean" value="true"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[36, 21]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 21]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="infoLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="MediaViewVideoPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="Default Cursor"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="playButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="progressLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="VolumeIcon">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.VolumeIcon.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSlider" name="audioSlider">
|
||||
<Properties>
|
||||
<Property name="majorTickSpacing" type="int" value="10"/>
|
||||
<Property name="maximum" type="int" value="50"/>
|
||||
<Property name="minorTickSpacing" type="int" value="5"/>
|
||||
<Property name="paintTicks" type="boolean" value="true"/>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.audioSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="value" type="int" value="25"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 21]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 21]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
|
||||
Regular → Executable
+471
-498
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Collections;
|
||||
@@ -172,9 +171,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
* Show the contents of the given AbstractFile as a visual image.
|
||||
*
|
||||
* @param file image file to show
|
||||
* @param dims dimension of the parent window (ignored)
|
||||
*/
|
||||
void showImageFx(final AbstractFile file, final Dimension dims) {
|
||||
void showImageFx(final AbstractFile file) {
|
||||
if (!fxInited) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,16 @@ public final class CoordinationService {
|
||||
}
|
||||
try {
|
||||
instance = new CoordinationService(rootNode);
|
||||
} catch (IOException | InterruptedException | KeeperException | CoordinationServiceException ex) {
|
||||
} catch (IOException | KeeperException | CoordinationServiceException ex) {
|
||||
throw new CoordinationServiceException("Failed to create coordination service", ex);
|
||||
} catch (InterruptedException ex) {
|
||||
/*
|
||||
* The interrupted exception should be propagated to support
|
||||
* task cancellation. To avoid a public API change here, restore
|
||||
* the interrupted flag and then throw the InterruptedException
|
||||
* in its wrapper.
|
||||
*/
|
||||
Thread.currentThread().interrupt();
|
||||
throw new CoordinationServiceException("Failed to create coordination service", ex);
|
||||
}
|
||||
}
|
||||
@@ -363,15 +372,22 @@ public final class CoordinationService {
|
||||
* @param category The desired category in the namespace.
|
||||
* @param nodePath The node to be deleted.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error deleting the
|
||||
* node.
|
||||
* @throws CoordinationServiceException If there is an error deleting the
|
||||
* node.
|
||||
* @throws java.lang.InterruptedException If a thread interrupt occurs while
|
||||
* blocked waiting for the operation
|
||||
* to complete.
|
||||
*/
|
||||
public void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException {
|
||||
public void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException {
|
||||
String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
|
||||
try {
|
||||
curator.delete().forPath(fullNodePath);
|
||||
} catch (Exception ex) {
|
||||
throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex);
|
||||
if (ex instanceof InterruptedException) {
|
||||
throw (InterruptedException) ex;
|
||||
} else {
|
||||
throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,15 +398,22 @@ public final class CoordinationService {
|
||||
*
|
||||
* @return A list of child node names.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error getting the
|
||||
* node list.
|
||||
* @throws CoordinationServiceException If there is an error getting the
|
||||
* node list.
|
||||
* @throws java.lang.InterruptedException If a thread interrupt occurs while
|
||||
* blocked waiting for the operation
|
||||
* to complete.
|
||||
*/
|
||||
public List<String> getNodeList(CategoryNode category) throws CoordinationServiceException {
|
||||
public List<String> getNodeList(CategoryNode category) throws CoordinationServiceException, InterruptedException {
|
||||
try {
|
||||
List<String> list = curator.getChildren().forPath(categoryNodeToPath.get(category.getDisplayName()));
|
||||
return list;
|
||||
} catch (Exception ex) {
|
||||
throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex);
|
||||
if (ex instanceof InterruptedException) {
|
||||
throw (InterruptedException) ex;
|
||||
} else {
|
||||
throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,9 +427,9 @@ public final class CoordinationService {
|
||||
*/
|
||||
private String getFullyQualifiedNodePath(CategoryNode category, String nodePath) {
|
||||
// nodePath on Unix systems starts with a "/" and ZooKeeper doesn't like two slashes in a row
|
||||
if(nodePath.startsWith("/")){
|
||||
if (nodePath.startsWith("/")) {
|
||||
return categoryNodeToPath.get(category.getDisplayName()) + nodePath.toUpperCase();
|
||||
}else{
|
||||
} else {
|
||||
return categoryNodeToPath.get(category.getDisplayName()) + "/" + nodePath.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.core;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import java.awt.Cursor;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -31,6 +34,7 @@ import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.modules.InstalledFileLocator;
|
||||
import org.openide.modules.ModuleInstall;
|
||||
import org.openide.util.NbBundle;
|
||||
@@ -74,6 +78,8 @@ public class Installer extends ModuleInstall {
|
||||
*/
|
||||
if (PlatformUtil.isWindowsOS()) {
|
||||
try {
|
||||
addGstreamerPathsToEnv();
|
||||
|
||||
//Note: if shipping with a different CRT version, this will only print a warning
|
||||
//and try to use linker mechanism to find the correct versions of libs.
|
||||
//We should update this if we officially switch to a new version of CRT/compiler
|
||||
@@ -271,6 +277,43 @@ public class Installer extends ModuleInstall {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Gstreamer bin and lib paths to the PATH environment variable so
|
||||
* that the correct plugins and libraries are found when Gstreamer is
|
||||
* initialized later.
|
||||
*/
|
||||
private static void addGstreamerPathsToEnv() {
|
||||
Path gstreamerPath = InstalledFileLocator.getDefault().locate("gstreamer", Installer.class.getPackage().getName(), false).toPath();
|
||||
|
||||
if (gstreamerPath == null) {
|
||||
logger.log(Level.SEVERE, "Failed to find GStreamer.");
|
||||
} else {
|
||||
String arch = "x86_64";
|
||||
if (!PlatformUtil.is64BitJVM()) {
|
||||
arch = "x86";
|
||||
}
|
||||
|
||||
Path gstreamerBasePath = Paths.get(gstreamerPath.toString(), "1.0", arch);
|
||||
Path gstreamerBinPath = Paths.get(gstreamerBasePath.toString(), "bin");
|
||||
Path gstreamerLibPath = Paths.get(gstreamerBasePath.toString(), "lib", "gstreamer-1.0");
|
||||
|
||||
// Update the PATH environment variable to contain the GStreamer
|
||||
// lib and bin paths.
|
||||
Kernel32 k32 = Kernel32.INSTANCE;
|
||||
String path = System.getenv("PATH");
|
||||
if (StringUtils.isBlank(path)) {
|
||||
k32.SetEnvironmentVariable("PATH", gstreamerLibPath.toString());
|
||||
} else {
|
||||
/*
|
||||
* Note that we *prepend* the paths so that the Gstreamer
|
||||
* binaries associated with the current release are found rather
|
||||
* than binaries associated with an earlier version of Autopsy.
|
||||
*/
|
||||
k32.SetEnvironmentVariable("PATH", gstreamerBinPath.toString() + File.pathSeparator + gstreamerLibPath.toString() + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a folder in the config directory for object detection classifiers if one does not
|
||||
* exist.
|
||||
@@ -288,7 +331,7 @@ public class Installer extends ModuleInstall {
|
||||
File pythonModulesDir = new File(PlatformUtil.getUserPythonModulesPath());
|
||||
pythonModulesDir.mkdir();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a folder in the config directory for Ocr Language Packs if one does
|
||||
* not exist.
|
||||
@@ -296,10 +339,10 @@ public class Installer extends ModuleInstall {
|
||||
private static void ensureOcrLanguagePacksFolderExists() {
|
||||
File ocrLanguagePacksDir = new File(PlatformUtil.getOcrLanguagePacksPath());
|
||||
boolean createDirectory = ocrLanguagePacksDir.mkdir();
|
||||
|
||||
|
||||
//If the directory did not exist, copy the tessdata folder over so we
|
||||
//support english.
|
||||
if(createDirectory) {
|
||||
if (createDirectory) {
|
||||
File tessdataDir = InstalledFileLocator.getDefault().locate(
|
||||
"Tesseract-OCR/tessdata", Installer.class.getPackage().getName(), false);
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -59,7 +59,19 @@ public final class ExecUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process terminator that can be used to kill a processes after it exceeds
|
||||
* A process terminator that can be used to kill a process spawned by a
|
||||
* thread that has been interrupted.
|
||||
*/
|
||||
public static class InterruptedThreadProcessTerminator implements ProcessTerminator {
|
||||
|
||||
@Override
|
||||
public boolean shouldTerminateProcess() {
|
||||
return Thread.currentThread().isInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A process terminator that can be used to kill a process after it exceeds
|
||||
* a maximum allowable run time.
|
||||
*/
|
||||
public static class TimedProcessTerminator implements ProcessTerminator {
|
||||
@@ -212,9 +224,6 @@ public final class ExecUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EVERYTHING FOLLOWING THIS LINE IS DEPRECATED AND SLATED FOR REMOVAL
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(ExecUtil.class.getName());
|
||||
private Process proc = null;
|
||||
private ExecUtil.StreamToStringRedirect errorStringRedirect = null;
|
||||
|
||||
@@ -47,6 +47,7 @@ public class AddDataSourceCallback extends DataSourceProcessorCallback {
|
||||
* @param caseForJob The case for the current job.
|
||||
* @param dataSourceInfo The data source
|
||||
* @param taskId The task id to associate with ingest job events.
|
||||
* @param lock The DSP lock
|
||||
*/
|
||||
public AddDataSourceCallback(Case caseForJob, AutoIngestDataSource dataSourceInfo, UUID taskId, Object lock) {
|
||||
this.caseForJob = caseForJob;
|
||||
|
||||
@@ -41,6 +41,8 @@ public class DataSourceProcessorUtility {
|
||||
* AutoIngestDataSourceProcessor interface are used.
|
||||
*
|
||||
* @param dataSourcePath Full path to the data source
|
||||
* @param processorCandidates Possible DSPs that can handle the data source
|
||||
*
|
||||
* @return Hash map of all DSPs that can process the data source along with
|
||||
* their confidence score
|
||||
* @throws
|
||||
|
||||
@@ -58,7 +58,7 @@ public class HealthMonitorDashboard {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName());
|
||||
|
||||
private final static String ADMIN_ACCESS_FILE_NAME = "_aiaa"; // NON-NLS
|
||||
private final static String ADMIN_ACCESS_FILE_NAME = "admin"; // NON-NLS
|
||||
private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_ACCESS_FILE_NAME).toString();
|
||||
|
||||
Map<String, List<HealthMonitor.DatabaseTimingResult>> timingData;
|
||||
|
||||
+21
-3
@@ -52,6 +52,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
private Blackboard blackboard;
|
||||
private double calculatedEntropy;
|
||||
private final double minimumEntropy;
|
||||
private IngestJobContext context;
|
||||
|
||||
/**
|
||||
* Create an EncryptionDetectionDataSourceIngestModule object that will
|
||||
@@ -67,6 +68,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
|
||||
validateSettings();
|
||||
blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Messages({
|
||||
@@ -77,8 +79,6 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
@Override
|
||||
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
|
||||
|
||||
|
||||
|
||||
try {
|
||||
if (dataSource instanceof Image) {
|
||||
|
||||
@@ -92,10 +92,23 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
int numVolSystemsChecked = 0;
|
||||
progressBar.progress(Bundle.EncryptionDetectionDataSourceIngestModule_processing_message(), 0);
|
||||
for (VolumeSystem volumeSystem : volumeSystems) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return ProcessResult.OK;
|
||||
}
|
||||
|
||||
for (Volume volume : volumeSystem.getVolumes()) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return ProcessResult.OK;
|
||||
}
|
||||
if (BitlockerDetection.isBitlockerVolume(volume)) {
|
||||
return flagVolume(volume, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionDataSourceIngestModule_artifactComment_bitlocker());
|
||||
}
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return ProcessResult.OK;
|
||||
}
|
||||
if (isVolumeEncrypted(volume)) {
|
||||
return flagVolume(volume, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, String.format(Bundle.EncryptionDetectionDataSourceIngestModule_artifactComment_suspected(), calculatedEntropy));
|
||||
}
|
||||
@@ -139,6 +152,11 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
* there was a problem.
|
||||
*/
|
||||
private IngestModule.ProcessResult flagVolume(Volume volume, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return ProcessResult.OK;
|
||||
}
|
||||
|
||||
try {
|
||||
BlackboardArtifact artifact = volume.newArtifact(artifactType);
|
||||
artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, EncryptionDetectionModuleFactory.getModuleName(), comment));
|
||||
@@ -198,7 +216,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
|
||||
*/
|
||||
if (volume.getFileSystems().isEmpty()) {
|
||||
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume);
|
||||
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume, context);
|
||||
if (calculatedEntropy >= minimumEntropy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+7
-1
@@ -82,6 +82,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
|
||||
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
|
||||
private FileTypeDetector fileTypeDetector;
|
||||
private Blackboard blackboard;
|
||||
private IngestJobContext context;
|
||||
private double calculatedEntropy;
|
||||
|
||||
private final double minimumEntropy;
|
||||
@@ -107,6 +108,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
|
||||
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
|
||||
try {
|
||||
validateSettings();
|
||||
this.context = context;
|
||||
blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard();
|
||||
fileTypeDetector = new FileTypeDetector();
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
@@ -194,6 +196,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
|
||||
*/
|
||||
private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
|
||||
try {
|
||||
if (context.fileIngestIsCancelled()) {
|
||||
return IngestModule.ProcessResult.OK;
|
||||
}
|
||||
|
||||
BlackboardArtifact artifact = file.newArtifact(artifactType);
|
||||
artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
|
||||
EncryptionDetectionModuleFactory.getModuleName(), comment));
|
||||
@@ -397,7 +403,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
|
||||
/*
|
||||
* Qualify the entropy.
|
||||
*/
|
||||
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
|
||||
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
|
||||
if (calculatedEntropy >= minimumEntropy) {
|
||||
possiblyEncrypted = true;
|
||||
}
|
||||
|
||||
+12
-1
@@ -22,6 +22,7 @@ import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@@ -69,6 +70,7 @@ final class EncryptionDetectionTools {
|
||||
* content as possibly encrypted.
|
||||
*
|
||||
* @param content The content to be calculated against.
|
||||
* @param context The ingest job context for cancellation checks
|
||||
*
|
||||
* @return The entropy of the content.
|
||||
*
|
||||
@@ -77,7 +79,7 @@ final class EncryptionDetectionTools {
|
||||
* @throws IOException If there is a failure closing or
|
||||
* reading from the InputStream.
|
||||
*/
|
||||
static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException {
|
||||
static double calculateEntropy(Content content, IngestJobContext context) throws ReadContentInputStream.ReadContentInputStreamException, IOException {
|
||||
/*
|
||||
* Logic in this method is based on
|
||||
* https://github.com/willjasen/entropy/blob/master/entropy.java
|
||||
@@ -95,8 +97,17 @@ final class EncryptionDetectionTools {
|
||||
*/
|
||||
int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE];
|
||||
int readByte;
|
||||
long bytesRead = 0;
|
||||
while ((readByte = bin.read()) != -1) {
|
||||
byteOccurences[readByte]++;
|
||||
|
||||
// Do a cancellation check every 10,000 bytes
|
||||
bytesRead++;
|
||||
if (bytesRead % 10000 == 0) {
|
||||
if (context.dataSourceIngestIsCancelled() || context.fileIngestIsCancelled()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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
|
||||
* 9
|
||||
* 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.progress;
|
||||
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.Cancellable;
|
||||
|
||||
/**
|
||||
* A progress indicator that displays progress using a progress bar in the lower
|
||||
* right hand corner of the application main frame, i.e., a NetBeans
|
||||
* ProgressHandle.
|
||||
*/
|
||||
public final class AppFrameProgressBar implements ProgressIndicator {
|
||||
|
||||
private final String displayName;
|
||||
private Cancellable cancellationBehavior;
|
||||
private ProgressHandle progressHandle;
|
||||
private volatile boolean cancelling;
|
||||
|
||||
/**
|
||||
* Constructs a progress indicator that displays progress using a progress
|
||||
* bar in the lower right hand corner of the application main frame, i.e., a
|
||||
* NetBeans ProgressHandle.
|
||||
*
|
||||
* @param displayName The display name for the progress bar (a fixed name
|
||||
* that appears above the current progress message).
|
||||
*/
|
||||
public AppFrameProgressBar(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cancellation behavior that should happen when a user clicks on
|
||||
* the "x" button of the progress bar.
|
||||
*
|
||||
* @param cancellationBehavior A org.openide.util.Cancellable that
|
||||
* implements the desired cancellation behavior.
|
||||
*/
|
||||
public void setCancellationBehavior(Cancellable cancellationBehavior) {
|
||||
this.cancellationBehavior = cancellationBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(String message, int totalWorkUnits) {
|
||||
cancelling = false;
|
||||
this.progressHandle = ProgressHandle.createHandle(displayName, cancellationBehavior);
|
||||
progressHandle.start(totalWorkUnits);
|
||||
progressHandle.progress(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(String message) {
|
||||
cancelling = false;
|
||||
this.progressHandle = ProgressHandle.createHandle(displayName, cancellationBehavior);
|
||||
progressHandle.start();
|
||||
progressHandle.progress(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchToIndeterminate(String message) {
|
||||
if (!cancelling) {
|
||||
progressHandle.switchToIndeterminate();
|
||||
progressHandle.progress(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) {
|
||||
if (!cancelling) {
|
||||
progressHandle.switchToDeterminate(totalWorkUnits);
|
||||
progressHandle.progress(message, workUnitsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(String message) {
|
||||
if (!cancelling) {
|
||||
progressHandle.progress(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(int workUnitsCompleted) {
|
||||
if (!cancelling) {
|
||||
progressHandle.progress(workUnitsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(String message, int workUnitsCompleted) {
|
||||
if (!cancelling) {
|
||||
progressHandle.progress(message, workUnitsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelling(String cancellingMessage) {
|
||||
cancelling = true;
|
||||
progressHandle.switchToIndeterminate();
|
||||
progressHandle.progress(cancellingMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
progressHandle.finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
# and open the template in the editor.
|
||||
|
||||
ProgressPanel.progressMessage.text=Message
|
||||
TaskCanceller.progress.cancellingMessage=Cancelling...
|
||||
|
||||
@@ -128,6 +128,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
|
||||
*
|
||||
* @param cancellingMessage
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setCancelling(String cancellingMessage) {
|
||||
cancelling = true;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
|
||||
@@ -49,7 +49,7 @@ public interface ProgressIndicator {
|
||||
*
|
||||
* @param message The initial progress message.
|
||||
*/
|
||||
public void switchToIndeterminate(String message);
|
||||
void switchToIndeterminate(String message);
|
||||
|
||||
/**
|
||||
* Switches the progress indicator to determinate mode (the total number of
|
||||
@@ -59,14 +59,14 @@ public interface ProgressIndicator {
|
||||
* @param workUnitsCompleted The number of work units completed so far.
|
||||
* @param totalWorkUnits The total number of work units to be completed.
|
||||
*/
|
||||
public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits);
|
||||
void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits);
|
||||
|
||||
/**
|
||||
* Updates the progress indicator with a progress message.
|
||||
*
|
||||
* @param message The progress message.
|
||||
*/
|
||||
public void progress(String message);
|
||||
void progress(String message);
|
||||
|
||||
/**
|
||||
* Updates the progress indicator with the number of work units completed so
|
||||
@@ -75,7 +75,7 @@ public interface ProgressIndicator {
|
||||
*
|
||||
* @param workUnitsCompleted Number of work units completed so far.
|
||||
*/
|
||||
public void progress(int workUnitsCompleted);
|
||||
void progress(int workUnitsCompleted);
|
||||
|
||||
/**
|
||||
* Updates the progress indicator with a progress message and the number of
|
||||
@@ -85,7 +85,24 @@ public interface ProgressIndicator {
|
||||
* @param message The progress message.
|
||||
* @param workUnitsCompleted Number of work units completed so far.
|
||||
*/
|
||||
public void progress(String message, int workUnitsCompleted);
|
||||
void progress(String message, int workUnitsCompleted);
|
||||
|
||||
/**
|
||||
* If the progress indicator supports cancelling the underlying task, sets a
|
||||
* cancelling message and causes the progress indicator to no longer accept
|
||||
* updates unless start is called again.
|
||||
*
|
||||
* The default implementation assumes that cancelling the underlying task is
|
||||
* not supported.
|
||||
*
|
||||
* @param cancellingMessage The cancelling messages.
|
||||
*/
|
||||
default void setCancelling(String cancellingMessage) {
|
||||
/*
|
||||
* The default implementation assumes that cancelling the underlying
|
||||
* task is not supported.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the progress indicator when the task is completed.
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.progress;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Pluggable cancellation behavior for use in progress indicators (such as the
|
||||
* application frame progress indicator) that support cancelling a task using an
|
||||
* implementation of org.openide.util.Cancellable. Encapsulates a Future<?> to
|
||||
* be cancelled and sets the cancelling flag and message of the progress
|
||||
* indicator.
|
||||
*/
|
||||
public class TaskCancellable implements Cancellable {
|
||||
|
||||
private final ProgressIndicator progress;
|
||||
private Future<?> future;
|
||||
|
||||
/**
|
||||
* Constructs a pluggable cancellation behavior for use in progress
|
||||
* indicators (such as the application frame progress indicator) that
|
||||
* support cancelling a task using an implementation of
|
||||
* org.openide.util.Cancellable. Encapsulates a Future<?> to be cancelled
|
||||
* and sets the cancelling flag and message of the progress indicator.
|
||||
*
|
||||
* @param progress
|
||||
*/
|
||||
public TaskCancellable(ProgressIndicator progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Future<?> used to cancel the associated task.
|
||||
*
|
||||
* @param future The future for the associated task.
|
||||
*/
|
||||
public synchronized void setFuture(Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"TaskCanceller.progress.cancellingMessage=Cancelling..."
|
||||
})
|
||||
public synchronized boolean cancel() {
|
||||
progress.setCancelling(Bundle.TaskCanceller_progress_cancellingMessage());
|
||||
return future.cancel(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,7 +37,7 @@ FileReportDataTypes.knownStatus.text=Known Status
|
||||
FileReportDataTypes.perms.text=Permissions
|
||||
FileReportDataTypes.path.text=Full Path
|
||||
FileReportText.getName.text=Files - Text
|
||||
FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case.
|
||||
FileReportText.getDesc.text=A delimited text file containing information about individual files in the case.
|
||||
ReportBodyFile.progress.querying=Querying files...
|
||||
ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\!
|
||||
ReportBodyFile.progress.loading=Loading files...
|
||||
@@ -258,3 +258,5 @@ CreatePortableCasePanel.outputFolderTextField.text=jTextField1
|
||||
CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder
|
||||
CreatePortableCasePanel.jLabel1.text=Export files tagged as:
|
||||
CreatePortableCasePanel.jLabel2.text=Select output folder:
|
||||
ReportFileTextConfigurationPanel.tabDelimitedButton.text=Tab delimited
|
||||
ReportFileTextConfigurationPanel.commaDelimitedButton.text=Comma delimited
|
||||
|
||||
@@ -85,7 +85,7 @@ FileReportDataTypes.knownStatus.text=Known Status
|
||||
FileReportDataTypes.perms.text=Permissions
|
||||
FileReportDataTypes.path.text=Full Path
|
||||
FileReportText.getName.text=Files - Text
|
||||
FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case.
|
||||
FileReportText.getDesc.text=A delimited text file containing information about individual files in the case.
|
||||
ReportBodyFile.progress.querying=Querying files...
|
||||
ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\!
|
||||
ReportBodyFile.progress.loading=Loading files...
|
||||
@@ -306,4 +306,6 @@ CreatePortableCasePanel.outputFolderTextField.text=jTextField1
|
||||
CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder
|
||||
CreatePortableCasePanel.jLabel1.text=Export files tagged as:
|
||||
CreatePortableCasePanel.jLabel2.text=Select output folder:
|
||||
ReportFileTextConfigurationPanel.tabDelimitedButton.text=Tab delimited
|
||||
ReportFileTextConfigurationPanel.commaDelimitedButton.text=Comma delimited
|
||||
TableReportGenerator.StatusColumn.Header=Review Status
|
||||
|
||||
@@ -230,6 +230,20 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up tracking to support any custom artifact or attribute types
|
||||
for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
|
||||
oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
|
||||
}
|
||||
for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
|
||||
try {
|
||||
oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
|
||||
} catch (TskCoreException ex) {
|
||||
handleError("Error looking up attribute name " + type.getLabel(),
|
||||
Bundle.CreatePortableCaseModule_generateReport_errorLookingUpAttrType(type.getLabel()),
|
||||
ex, progressPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the tagged files
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
@@ -245,20 +259,6 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up tracking to support any custom artifact or attribute types
|
||||
for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
|
||||
oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
|
||||
}
|
||||
for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
|
||||
try {
|
||||
oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
|
||||
} catch (TskCoreException ex) {
|
||||
handleError("Error looking up attribute name " + type.getLabel(),
|
||||
Bundle.CreatePortableCaseModule_generateReport_errorLookingUpAttrType(type.getLabel()),
|
||||
ex, progressPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the tagged artifacts and associated files
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
@@ -518,7 +518,7 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
* Get the artifact type ID in the portable case and create new artifact type if needed.
|
||||
* For built-in artifacts this will be the same as the original.
|
||||
*
|
||||
* @param oldArtifactTypeId The artifact type ID in the current case
|
||||
* @param oldArtifact The artifact in the current case
|
||||
*
|
||||
* @return The corresponding artifact type ID in the portable case
|
||||
*/
|
||||
@@ -541,7 +541,7 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
* Get the attribute type ID in the portable case and create new attribute type if needed.
|
||||
* For built-in attributes this will be the same as the original.
|
||||
*
|
||||
* @param oldAttributeTypeId The attribute type ID in the current case
|
||||
* @param oldAttribute The attribute in the current case
|
||||
*
|
||||
* @return The corresponding attribute type in the portable case
|
||||
*/
|
||||
@@ -577,30 +577,19 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
})
|
||||
private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
|
||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
|
||||
|
||||
long newFileId;
|
||||
CaseDbTransaction trans = portableSkCase.beginTransaction();
|
||||
try {
|
||||
newFileId = copyContent(content, trans);
|
||||
trans.commit();
|
||||
return newFileId;
|
||||
} catch (TskCoreException ex) {
|
||||
trans.rollback();
|
||||
throw(ex);
|
||||
}
|
||||
return copyContent(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object ID for the given content object in the portable case.
|
||||
*
|
||||
* @param content The content object to copy into the portable case
|
||||
* @param trans The current transaction
|
||||
*
|
||||
* @return the new object ID for this content
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private long copyContent(Content content, CaseDbTransaction trans) throws TskCoreException {
|
||||
private long copyContent(Content content) throws TskCoreException {
|
||||
|
||||
// Check if we've already copied this content
|
||||
if (oldIdToNewContent.containsKey(content.getId())) {
|
||||
@@ -612,67 +601,82 @@ public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
// - Copy this content
|
||||
long parentId = 0;
|
||||
if (content.getParent() != null) {
|
||||
parentId = copyContent(content.getParent(), trans);
|
||||
parentId = copyContent(content.getParent());
|
||||
}
|
||||
|
||||
Content newContent;
|
||||
if (content instanceof Image) {
|
||||
Image image = (Image)content;
|
||||
newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
|
||||
new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
|
||||
} else if (content instanceof VolumeSystem) {
|
||||
VolumeSystem vs = (VolumeSystem)content;
|
||||
newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
|
||||
} else if (content instanceof Volume) {
|
||||
Volume vs = (Volume)content;
|
||||
newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
|
||||
vs.getDescription(), vs.getFlags(), trans);
|
||||
} else if (content instanceof FileSystem) {
|
||||
FileSystem fs = (FileSystem)content;
|
||||
newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
|
||||
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
|
||||
fs.getName(), trans);
|
||||
} else if (content instanceof AbstractFile) {
|
||||
AbstractFile abstractFile = (AbstractFile)content;
|
||||
|
||||
if (abstractFile instanceof LocalFilesDataSource) {
|
||||
LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
|
||||
newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
|
||||
} else {
|
||||
if (abstractFile.isDir()) {
|
||||
newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
|
||||
} else {
|
||||
try {
|
||||
// Copy the file
|
||||
String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
|
||||
String exportSubFolder = getExportSubfolder(abstractFile);
|
||||
File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
|
||||
File localFile = new File(exportFolder, fileName);
|
||||
ContentUtils.writeToFile(abstractFile, localFile);
|
||||
|
||||
// Get the new parent object in the portable case database
|
||||
Content oldParent = abstractFile.getParent();
|
||||
if (! oldIdToNewContent.containsKey(oldParent.getId())) {
|
||||
throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created");
|
||||
}
|
||||
Content newParent = oldIdToNewContent.get(oldParent.getId());
|
||||
|
||||
// Construct the relative path to the copied file
|
||||
String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
|
||||
|
||||
newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
|
||||
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
|
||||
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
|
||||
true, TskData.EncodingType.NONE,
|
||||
newParent, trans);
|
||||
} catch (IOException ex) {
|
||||
throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
|
||||
+ abstractFile.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (content instanceof BlackboardArtifact) {
|
||||
BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
|
||||
newContent = copyArtifact(parentId, artifactToCopy);
|
||||
} else {
|
||||
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName());
|
||||
CaseDbTransaction trans = portableSkCase.beginTransaction();
|
||||
try {
|
||||
if (content instanceof Image) {
|
||||
Image image = (Image)content;
|
||||
newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
|
||||
new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
|
||||
} else if (content instanceof VolumeSystem) {
|
||||
VolumeSystem vs = (VolumeSystem)content;
|
||||
newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
|
||||
} else if (content instanceof Volume) {
|
||||
Volume vs = (Volume)content;
|
||||
newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
|
||||
vs.getDescription(), vs.getFlags(), trans);
|
||||
} else if (content instanceof FileSystem) {
|
||||
FileSystem fs = (FileSystem)content;
|
||||
newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
|
||||
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
|
||||
fs.getName(), trans);
|
||||
} else if (content instanceof BlackboardArtifact) {
|
||||
BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
|
||||
newContent = copyArtifact(parentId, artifactToCopy);
|
||||
} else if (content instanceof AbstractFile) {
|
||||
AbstractFile abstractFile = (AbstractFile)content;
|
||||
|
||||
if (abstractFile instanceof LocalFilesDataSource) {
|
||||
LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
|
||||
newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
|
||||
} else {
|
||||
if (abstractFile.isDir()) {
|
||||
newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
|
||||
} else {
|
||||
try {
|
||||
// Copy the file
|
||||
String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
|
||||
String exportSubFolder = getExportSubfolder(abstractFile);
|
||||
File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
|
||||
File localFile = new File(exportFolder, fileName);
|
||||
ContentUtils.writeToFile(abstractFile, localFile);
|
||||
|
||||
// Get the new parent object in the portable case database
|
||||
Content oldParent = abstractFile.getParent();
|
||||
if (! oldIdToNewContent.containsKey(oldParent.getId())) {
|
||||
throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created");
|
||||
}
|
||||
Content newParent = oldIdToNewContent.get(oldParent.getId());
|
||||
|
||||
// Construct the relative path to the copied file
|
||||
String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
|
||||
|
||||
newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
|
||||
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
|
||||
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
|
||||
true, TskData.EncodingType.NONE,
|
||||
newParent, trans);
|
||||
} catch (IOException ex) {
|
||||
throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
|
||||
+ abstractFile.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName());
|
||||
}
|
||||
trans.commit();
|
||||
} catch (TskCoreException ex) {
|
||||
trans.rollback();
|
||||
throw(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the new object
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 - 2018 Basis Technology Corp.
|
||||
* Copyright 2013 - 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.report;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
@@ -27,6 +28,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
@@ -36,18 +38,18 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A Tab-delimited text report of the files in the case.
|
||||
* A delimited text report of the files in the case.
|
||||
*
|
||||
* @author jwallace
|
||||
*/
|
||||
class FileReportText implements FileReportModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FileReportText.class.getName());
|
||||
private static final String FILE_NAME = "file-report.txt"; //NON-NLS
|
||||
private static FileReportText instance;
|
||||
private String reportPath;
|
||||
private Writer out;
|
||||
private static final String FILE_NAME = "file-report.txt"; //NON-NLS
|
||||
|
||||
private static FileReportText instance;
|
||||
private ReportFileTextConfigurationPanel configPanel;
|
||||
|
||||
// Get the default implementation of this report
|
||||
public static synchronized FileReportText getDefault() {
|
||||
@@ -62,7 +64,7 @@ class FileReportText implements FileReportModule {
|
||||
this.reportPath = baseReportDir + FILE_NAME;
|
||||
try {
|
||||
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath)));
|
||||
} catch (IOException ex) {
|
||||
} catch (FileNotFoundException ex) {
|
||||
logger.log(Level.WARNING, "Failed to create report text file", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
@@ -85,11 +87,12 @@ class FileReportText implements FileReportModule {
|
||||
}
|
||||
}
|
||||
|
||||
private String getTabDelimitedList(List<String> list) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
private String getDelimitedList(List<String> list, String delimiter) {
|
||||
StringBuilder output;
|
||||
output = new StringBuilder();
|
||||
Iterator<String> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
output.append(it.next()).append((it.hasNext() ? "\t" : System.lineSeparator()));
|
||||
output.append('"').append(it.next()).append('"').append((it.hasNext() ? delimiter : System.lineSeparator()));
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
@@ -101,7 +104,7 @@ class FileReportText implements FileReportModule {
|
||||
titles.add(col.getName());
|
||||
}
|
||||
try {
|
||||
out.write(getTabDelimitedList(titles));
|
||||
out.write(getDelimitedList(titles, configPanel.getDelimiter()));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error when writing headers to report file: {0}", ex); //NON-NLS
|
||||
}
|
||||
@@ -114,7 +117,7 @@ class FileReportText implements FileReportModule {
|
||||
cells.add(type.getValue(toAdd));
|
||||
}
|
||||
try {
|
||||
out.write(getTabDelimitedList(cells));
|
||||
out.write(getDelimitedList(cells, configPanel.getDelimiter()));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error when writing row to report file: {0}", ex); //NON-NLS
|
||||
}
|
||||
@@ -143,4 +146,12 @@ class FileReportText implements FileReportModule {
|
||||
public String getRelativeFilePath() {
|
||||
return FILE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPanel getConfigurationPanel() {
|
||||
if (configPanel == null) {
|
||||
configPanel = new ReportFileTextConfigurationPanel();
|
||||
}
|
||||
return configPanel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<NonVisualComponents>
|
||||
<Component class="javax.swing.ButtonGroup" name="delimiterGroup">
|
||||
</Component>
|
||||
</NonVisualComponents>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="tabDelimitedButton" min="-2" pref="116" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="commaDelimitedButton" min="-2" pref="133" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="166" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="tabDelimitedButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="commaDelimitedButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="78" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JRadioButton" name="tabDelimitedButton">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="delimiterGroup"/>
|
||||
</Property>
|
||||
<Property name="selected" type="boolean" value="true"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="ReportFileTextConfigurationPanel.tabDelimitedButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="commaDelimitedButton">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="delimiterGroup"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="ReportFileTextConfigurationPanel.commaDelimitedButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 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.report;
|
||||
|
||||
/**
|
||||
* Panel for configuring settings for the file text report
|
||||
*/
|
||||
class ReportFileTextConfigurationPanel extends javax.swing.JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAB_DELIMITER = "\t"; //NON-NLS
|
||||
private static final String COMMA_DELIMITER = ","; //NON-NLS
|
||||
|
||||
/**
|
||||
* Creates new form ReportFileTextConfigurationPanel
|
||||
*/
|
||||
ReportFileTextConfigurationPanel() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delimiter that was selected on this panel
|
||||
*
|
||||
* @return the selected delimiter
|
||||
*/
|
||||
String getDelimiter() {
|
||||
if (commaDelimitedButton.isSelected()) {
|
||||
return COMMA_DELIMITER;
|
||||
} else {
|
||||
//if the comma button is not selected default to tab since it was previously the only option
|
||||
return TAB_DELIMITER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
delimiterGroup = new javax.swing.ButtonGroup();
|
||||
tabDelimitedButton = new javax.swing.JRadioButton();
|
||||
commaDelimitedButton = new javax.swing.JRadioButton();
|
||||
|
||||
delimiterGroup.add(tabDelimitedButton);
|
||||
tabDelimitedButton.setSelected(true);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(tabDelimitedButton, org.openide.util.NbBundle.getMessage(ReportFileTextConfigurationPanel.class, "ReportFileTextConfigurationPanel.tabDelimitedButton.text")); // NOI18N
|
||||
|
||||
delimiterGroup.add(commaDelimitedButton);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(commaDelimitedButton, org.openide.util.NbBundle.getMessage(ReportFileTextConfigurationPanel.class, "ReportFileTextConfigurationPanel.commaDelimitedButton.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()
|
||||
.addContainerGap()
|
||||
.addComponent(tabDelimitedButton, javax.swing.GroupLayout.PREFERRED_SIZE, 116, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(commaDelimitedButton, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(166, Short.MAX_VALUE))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(tabDelimitedButton)
|
||||
.addComponent(commaDelimitedButton))
|
||||
.addContainerGap(78, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JRadioButton commaDelimitedButton;
|
||||
private javax.swing.ButtonGroup delimiterGroup;
|
||||
private javax.swing.JRadioButton tabDelimitedButton;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -59,7 +59,7 @@ import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
class TableReportGenerator {
|
||||
class TableReportGenerator {
|
||||
|
||||
private final List<BlackboardArtifact.Type> artifactTypes = new ArrayList<>();
|
||||
private final HashSet<String> tagNamesFilter = new HashSet<>();
|
||||
@@ -288,7 +288,7 @@ class TableReportGenerator {
|
||||
ArrayList<String> columnHeaders = new ArrayList<>(Arrays.asList(
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.tag"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.file"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.userName"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeModified"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeChanged"),
|
||||
@@ -389,7 +389,7 @@ class TableReportGenerator {
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.resultType"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.tag"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.comment"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile"),
|
||||
NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.userName"))));
|
||||
|
||||
// Give the modules the rows for the content tags.
|
||||
@@ -400,7 +400,7 @@ class TableReportGenerator {
|
||||
}
|
||||
|
||||
List<String> row;
|
||||
row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName() + notableString,
|
||||
row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName() + notableString,
|
||||
tag.getComment(), tag.getContent().getName(), tag.getUserName()));
|
||||
tableReport.addRow(row);
|
||||
|
||||
@@ -528,7 +528,7 @@ class TableReportGenerator {
|
||||
* @param tableModule module to report on
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@NbBundle.Messages ({"ReportGenerator.errList.noOpenCase=No open case available."})
|
||||
@NbBundle.Messages({"ReportGenerator.errList.noOpenCase=No open case available."})
|
||||
private void writeKeywordHits(TableReportModule tableModule, String comment, HashSet<String> tagNamesFilter) {
|
||||
|
||||
// Query for keyword lists-only so that we can tell modules what lists
|
||||
@@ -545,24 +545,24 @@ class TableReportGenerator {
|
||||
logger.log(Level.SEVERE, "Exception while getting open case: ", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Get a list of all selected tag IDs
|
||||
String tagIDList = "";
|
||||
if( ! tagNamesFilter.isEmpty()) {
|
||||
if (!tagNamesFilter.isEmpty()) {
|
||||
try {
|
||||
Map<String, TagName> tagNamesMap = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap();
|
||||
for(String tagDisplayName : tagNamesFilter) {
|
||||
if(tagNamesMap.containsKey(tagDisplayName)) {
|
||||
if (! tagIDList.isEmpty()) {
|
||||
for (String tagDisplayName : tagNamesFilter) {
|
||||
if (tagNamesMap.containsKey(tagDisplayName)) {
|
||||
if (!tagIDList.isEmpty()) {
|
||||
tagIDList += ",";
|
||||
}
|
||||
tagIDList += tagNamesMap.get(tagDisplayName).getId();
|
||||
} else {
|
||||
// If the tag name ends with "(Notable)", try stripping that off
|
||||
if(tagDisplayName.endsWith(getNotableTagLabel())) {
|
||||
if (tagDisplayName.endsWith(getNotableTagLabel())) {
|
||||
String editedDisplayName = tagDisplayName.substring(0, tagDisplayName.length() - getNotableTagLabel().length());
|
||||
if(tagNamesMap.containsKey(editedDisplayName)) {
|
||||
if (! tagIDList.isEmpty()) {
|
||||
if (tagNamesMap.containsKey(editedDisplayName)) {
|
||||
if (!tagIDList.isEmpty()) {
|
||||
tagIDList += ",";
|
||||
}
|
||||
tagIDList += tagNamesMap.get(editedDisplayName).getId();
|
||||
@@ -575,9 +575,10 @@ class TableReportGenerator {
|
||||
tagIDList = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check if there are any ad-hoc results
|
||||
String adHocCountQuery = "SELECT COUNT(*) FROM " + //NON-NLS
|
||||
String adHocCountQuery = "SELECT COUNT(*) FROM "
|
||||
+ //NON-NLS
|
||||
"(SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 ";//NON-NLS
|
||||
if (!tagIDList.isEmpty()) {
|
||||
adHocCountQuery += ", blackboard_artifact_tags as tag "; //NON-NLS
|
||||
@@ -586,7 +587,8 @@ class TableReportGenerator {
|
||||
if (!tagIDList.isEmpty()) {
|
||||
adHocCountQuery += " AND (art.artifact_id = tag.artifact_id) AND (tag.tag_name_id IN (" + tagIDList + ")) "; //NON-NLS
|
||||
}
|
||||
adHocCountQuery += "EXCEPT " + // NON-NLS
|
||||
adHocCountQuery += "EXCEPT "
|
||||
+ // NON-NLS
|
||||
"SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ")) AS adHocHits"; //NON-NLS
|
||||
|
||||
int adHocCount = 0;
|
||||
@@ -602,7 +604,7 @@ class TableReportGenerator {
|
||||
logger.log(Level.SEVERE, "Failed to count ad hoc searches with query " + adHocCountQuery, ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Create the query to get the keyword list names
|
||||
if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) {
|
||||
orderByClause = "ORDER BY convert_to(list, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS
|
||||
@@ -613,7 +615,7 @@ class TableReportGenerator {
|
||||
= "SELECT att.value_text AS list "
|
||||
+ //NON-NLS
|
||||
"FROM blackboard_attributes AS att, blackboard_artifacts AS art "; // NON-NLS
|
||||
if(! tagIDList.isEmpty()) {
|
||||
if (!tagIDList.isEmpty()) {
|
||||
keywordListQuery += ", blackboard_artifact_tags as tag "; //NON-NLS
|
||||
}
|
||||
keywordListQuery += "WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " "
|
||||
@@ -621,8 +623,9 @@ class TableReportGenerator {
|
||||
"AND art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " "
|
||||
+ //NON-NLS
|
||||
"AND att.artifact_id = art.artifact_id ";
|
||||
if (! tagIDList.isEmpty()) {
|
||||
keywordListQuery += "AND (art.artifact_id = tag.artifact_id) " + //NON-NLS
|
||||
if (!tagIDList.isEmpty()) {
|
||||
keywordListQuery += "AND (art.artifact_id = tag.artifact_id) "
|
||||
+ //NON-NLS
|
||||
"AND (tag.tag_name_id IN (" + tagIDList + ")) "; //NON-NLS
|
||||
}
|
||||
if (adHocCount > 0) {
|
||||
@@ -665,7 +668,7 @@ class TableReportGenerator {
|
||||
} else {
|
||||
orderByClause = "ORDER BY list ASC, keyword ASC, parent_path ASC, name ASC, preview ASC"; //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
// Query for keywords that are part of a list
|
||||
String keywordListsQuery
|
||||
= "SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path "
|
||||
@@ -687,22 +690,33 @@ class TableReportGenerator {
|
||||
"AND (att3.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") "
|
||||
+ //NON-NLS
|
||||
"AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") ";
|
||||
|
||||
|
||||
// Query for keywords that are not part of a list
|
||||
String keywordAdHocQuery =
|
||||
"SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, \'\' AS list, f.name AS name, f.parent_path AS parent_path " + // NON-NLS
|
||||
"FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, tsk_files AS f " + // NON-NLS
|
||||
"WHERE " + // NON-NLS
|
||||
" (art.artifact_id IN (SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " + // NON-NLS
|
||||
"EXCEPT " + // NON-NLS
|
||||
"SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "))) " + //NON-NLS
|
||||
"AND (att1.artifact_id = art.artifact_id) " + //NON-NLS
|
||||
"AND (att2.artifact_id = art.artifact_id) " + //NON-NLS
|
||||
"AND (f.obj_id = art.obj_id) " + //NON-NLS
|
||||
"AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") " + // NON-NLS
|
||||
"AND (att2.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") " + // NON-NLS
|
||||
String keywordAdHocQuery
|
||||
= "SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, \'\' AS list, f.name AS name, f.parent_path AS parent_path "
|
||||
+ // NON-NLS
|
||||
"FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, tsk_files AS f "
|
||||
+ // NON-NLS
|
||||
"WHERE "
|
||||
+ // NON-NLS
|
||||
" (art.artifact_id IN (SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "
|
||||
+ // NON-NLS
|
||||
"EXCEPT "
|
||||
+ // NON-NLS
|
||||
"SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "))) "
|
||||
+ //NON-NLS
|
||||
"AND (att1.artifact_id = art.artifact_id) "
|
||||
+ //NON-NLS
|
||||
"AND (att2.artifact_id = art.artifact_id) "
|
||||
+ //NON-NLS
|
||||
"AND (f.obj_id = art.obj_id) "
|
||||
+ //NON-NLS
|
||||
"AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") "
|
||||
+ // NON-NLS
|
||||
"AND (att2.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") "
|
||||
+ // NON-NLS
|
||||
"AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "; // NON-NLS
|
||||
|
||||
|
||||
String keywordsQuery = "SELECT * FROM ( " + keywordListsQuery + " UNION " + keywordAdHocQuery + " ) kwHits " + orderByClause;
|
||||
|
||||
try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordsQuery)) {
|
||||
@@ -760,7 +774,7 @@ class TableReportGenerator {
|
||||
if (!currentKeyword.equals("")) {
|
||||
tableModule.endTable();
|
||||
}
|
||||
|
||||
|
||||
// Prepare for a new table.
|
||||
currentKeyword = keyword;
|
||||
tableModule.addSetElement(currentKeyword);
|
||||
@@ -773,7 +787,7 @@ class TableReportGenerator {
|
||||
|
||||
tableModule.addRow(Arrays.asList(new String[]{preview, uniquePath, tagsList}));
|
||||
}
|
||||
|
||||
|
||||
// End the previous table if one exists.
|
||||
if (!currentKeyword.isEmpty()) {
|
||||
tableModule.endTable();
|
||||
@@ -1242,6 +1256,7 @@ class TableReportGenerator {
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME)));
|
||||
|
||||
attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == artifactTypeId) {
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)));
|
||||
@@ -1249,6 +1264,7 @@ class TableReportGenerator {
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)));
|
||||
|
||||
attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() == artifactTypeId) {
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME)));
|
||||
@@ -1509,8 +1525,8 @@ class TableReportGenerator {
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mailServer"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SERVER_NAME)));
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() == artifactTypeId ||
|
||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID() == artifactTypeId) {
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() == artifactTypeId
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID() == artifactTypeId) {
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME)));
|
||||
|
||||
@@ -1570,7 +1586,7 @@ class TableReportGenerator {
|
||||
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskPath"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)));
|
||||
|
||||
|
||||
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.comment"),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)));
|
||||
|
||||
@@ -1647,6 +1663,8 @@ class TableReportGenerator {
|
||||
attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
|
||||
attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID));
|
||||
} else if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID()) {
|
||||
attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
} else {
|
||||
// This is the case that it is a custom type. The reason an else is
|
||||
// necessary is to make sure that the source file column is added
|
||||
|
||||
@@ -248,7 +248,7 @@ public final class CaseUcoFormatExporter {
|
||||
* @param selectedDataSourceId Object ID of the data source
|
||||
* @param caseTraceId CASE-UCO trace ID object for the Autopsy case entry
|
||||
* @param skCase SleuthkitCase object
|
||||
* @param catalog JsonGenerator object
|
||||
* @param jsonGenerator JsonGenerator object
|
||||
* @return
|
||||
* @throws TskCoreException
|
||||
* @throws SQLException
|
||||
|
||||
@@ -62,8 +62,6 @@ public abstract class UiSnapShotReportWriter {
|
||||
* report.
|
||||
* @param reportName The name of the report.
|
||||
* @param generationDate The generation Date of the report.
|
||||
* @param snapshot A snapshot of the view to include in the
|
||||
* report.
|
||||
*/
|
||||
protected UiSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate) {
|
||||
this.currentCase = currentCase;
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.sleuthkit.datamodel.Report;
|
||||
/**
|
||||
* Factory for creating TextExtractors given a Content instance
|
||||
*
|
||||
* See {@link org.sleuthkit.autopsy.textextractors.textextractorconfigs} for
|
||||
* See {@link org.sleuthkit.autopsy.textextractors.configs} for
|
||||
* available extractor configuration options.
|
||||
*
|
||||
* @see org.openide.util.Lookup
|
||||
@@ -40,7 +40,7 @@ public class TextExtractorFactory {
|
||||
* Returns a TextExtractor containing the Content text. Configuration files
|
||||
* can be added to the Lookup.
|
||||
*
|
||||
* See {@link org.sleuthkit.autopsy.textextractors.textextractorconfigs} for
|
||||
* See {@link org.sleuthkit.autopsy.textextractors.configs} for
|
||||
* available extractor configuration options.
|
||||
*
|
||||
* @param content Content source that will be read from
|
||||
@@ -124,7 +124,7 @@ public class TextExtractorFactory {
|
||||
* getExtractor(Content, Lookup).
|
||||
*
|
||||
* Configure this extractor with the StringsConfig in
|
||||
* {@link org.sleuthkit.autopsy.textextractors.textextractorconfigs}
|
||||
* {@link org.sleuthkit.autopsy.textextractors.configs}
|
||||
*
|
||||
* @param content Content source to read from
|
||||
* @param context Contains extraction configurations for certain file types
|
||||
|
||||
@@ -72,6 +72,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
@@ -711,10 +712,10 @@ public class EventsRepository {
|
||||
// if the time is legitimate ( greater than zero ) insert it into the db
|
||||
if (eventDescription != null && eventDescription.getTime() > 0) {
|
||||
long objectID = bbart.getObjectID();
|
||||
AbstractFile f = skCase.getAbstractFileById(objectID);
|
||||
long datasourceID = f.getDataSource().getId();
|
||||
Content content = skCase.getContentById(objectID);
|
||||
long datasourceID = content.getDataSource().getId();
|
||||
long artifactID = bbart.getArtifactID();
|
||||
Set<String> hashSets = f.getHashSetNames();
|
||||
Set<String> hashSets = content.getHashSetNames();
|
||||
List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbart);
|
||||
String fullDescription = eventDescription.getFullDescription();
|
||||
String medDescription = eventDescription.getMedDescription();
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
<dependency org="com.apple" name="AppleJavaExtensions" rev="1.4"/>
|
||||
|
||||
<!-- for viewers -->
|
||||
<dependency conf="autopsy_core->*" org="org.freedesktop.gstreamer" name="gst1-java-core" rev="0.9.3"/>
|
||||
<dependency conf="autopsy_core->*" org="org.freedesktop.gstreamer" name="gst1-java-core" rev="1.0.0"/>
|
||||
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="jna" rev="3.4.0"/>
|
||||
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="platform" rev="3.4.0"/>
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ file.reference.controlsfx-8.40.11.jar=release/modules/ext/controlsfx-8.40.11.jar
|
||||
file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar
|
||||
file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar
|
||||
file.reference.gson-2.8.1.jar=release/modules/ext/gson-2.8.1.jar
|
||||
file.reference.gstreamer-java-1.5.jar=release/modules/ext/gstreamer-java-1.5.jar
|
||||
file.reference.gst1-java-core-0.9.3.jar=release/modules/ext/gst1-java-core-0.9.3.jar
|
||||
file.reference.gst1-java-core-1.0.0.jar=release\\modules\\ext\\gst1-java-core-1.0.0.jar
|
||||
file.reference.jna-3.4.0.jar=release/modules/ext/jna-3.4.0.jar
|
||||
file.reference.guava-19.0.jar=release/modules/ext/guava-19.0.jar
|
||||
file.reference.imageio-bmp-3.2.jar=release/modules/ext/imageio-bmp-3.2.jar
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
<package>org.apache.commons.io.input</package>
|
||||
<package>org.apache.commons.io.monitor</package>
|
||||
<package>org.apache.commons.io.output</package>
|
||||
<package>org.apache.commons.io.serialization</package>
|
||||
<package>org.apache.commons.lang</package>
|
||||
<package>org.apache.commons.lang.builder</package>
|
||||
<package>org.apache.commons.lang.enums</package>
|
||||
@@ -589,23 +590,16 @@
|
||||
<package>org.dom4j.xpath</package>
|
||||
<package>org.dom4j.xpp</package>
|
||||
<package>org.freedesktop.gstreamer</package>
|
||||
<package>org.freedesktop.gstreamer.controller</package>
|
||||
<package>org.freedesktop.gstreamer.device</package>
|
||||
<package>org.freedesktop.gstreamer.elements</package>
|
||||
<package>org.freedesktop.gstreamer.elements.good</package>
|
||||
<package>org.freedesktop.gstreamer.event</package>
|
||||
<package>org.freedesktop.gstreamer.example</package>
|
||||
<package>org.freedesktop.gstreamer.glib</package>
|
||||
<package>org.freedesktop.gstreamer.interfaces</package>
|
||||
<package>org.freedesktop.gstreamer.io</package>
|
||||
<package>org.freedesktop.gstreamer.lowlevel</package>
|
||||
<package>org.freedesktop.gstreamer.lowlevel.annotations</package>
|
||||
<package>org.freedesktop.gstreamer.media</package>
|
||||
<package>org.freedesktop.gstreamer.media.event</package>
|
||||
<package>org.freedesktop.gstreamer.message</package>
|
||||
<package>org.freedesktop.gstreamer.query</package>
|
||||
<package>org.freedesktop.gstreamer.swing</package>
|
||||
<package>org.freedesktop.gstreamer.swt</package>
|
||||
<package>org.freedesktop.gstreamer.swt.overlay</package>
|
||||
<package>org.freedesktop.gstreamer.webrtc</package>
|
||||
<package>org.hyperic.jni</package>
|
||||
<package>org.hyperic.sigar</package>
|
||||
<package>org.hyperic.sigar.cmd</package>
|
||||
@@ -707,6 +701,10 @@
|
||||
<runtime-relative-path>ext/logkit-1.0.1.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/logkit-1.0.1.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/gst1-java-core-1.0.0.jar</runtime-relative-path>
|
||||
<binary-origin>release\modules\ext\gst1-java-core-1.0.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/imageio-jpeg-3.2.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/imageio-jpeg-3.2.jar</binary-origin>
|
||||
@@ -967,10 +965,6 @@
|
||||
<runtime-relative-path>ext/common-lang-3.2.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/common-lang-3.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/gst1-java-core-0.9.3.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/gst1-java-core-0.9.3.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/dd-plist-1.20.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/dd-plist-1.20.jar</binary-origin>
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.netbeans.swing.outline.DefaultOutlineModel;
|
||||
import org.netbeans.swing.outline.Outline;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode;
|
||||
|
||||
/**
|
||||
* A panel which displays an outline view with all auto ingest nodes and their
|
||||
|
||||
-69
@@ -293,75 +293,6 @@ final class AutoIngestAdminActions {
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case",
|
||||
"AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case."})
|
||||
static final class DeleteCaseAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final AutoIngestJob job;
|
||||
|
||||
DeleteCaseAction(AutoIngestJob selectedJob) {
|
||||
super(Bundle.AutoIngestAdminActions_deleteCaseAction_title());
|
||||
this.job = selectedJob;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (job == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID);
|
||||
if (tc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoIngestDashboard dashboard = tc.getAutoIngestDashboard();
|
||||
if (dashboard != null) {
|
||||
String caseName = job.getManifest().getCaseName();
|
||||
|
||||
Object[] options = {
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.Delete"),
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotDelete")
|
||||
};
|
||||
Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?"};
|
||||
int reply = JOptionPane.showOptionDialog(dashboard,
|
||||
msgContent,
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmDeletionHeader"),
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[JOptionPane.NO_OPTION]);
|
||||
if (reply == JOptionPane.YES_OPTION) {
|
||||
EventQueue.invokeLater(() -> {
|
||||
dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job);
|
||||
|
||||
dashboard.getCompletedJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshChildrenEvent(dashboard.getMonitor()));
|
||||
dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
if (AutoIngestManager.CaseDeletionResult.FAILED == result) {
|
||||
JOptionPane.showMessageDialog(dashboard,
|
||||
String.format("Could not delete case %s. It may be in use.", caseName),
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
} else if (AutoIngestManager.CaseDeletionResult.PARTIALLY_DELETED == result) {
|
||||
JOptionPane.showMessageDialog(dashboard,
|
||||
String.format("Could not fully delete case %s. See log for details.", caseName),
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone(); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log",
|
||||
"AutoIngestAdminActions.showCaseLogActionFailed.title=Unable to display case log",
|
||||
"AutoIngestAdminActions.showCaseLogActionFailed.message=Case log file does not exist",
|
||||
|
||||
+9
-32
@@ -57,14 +57,15 @@
|
||||
<Component id="runningScrollPane" min="-2" pref="1021" max="-2" attributes="0"/>
|
||||
<Component id="completedScrollPane" min="-2" pref="1021" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="bnCancelJob" linkSize="1" max="32767" attributes="0"/>
|
||||
<Component id="bnShowProgress" linkSize="1" max="32767" attributes="0"/>
|
||||
<Component id="bnCancelModule" linkSize="1" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="bnDeleteCase" linkSize="1" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="bnShowCaseLog" max="32767" attributes="0"/>
|
||||
<Component id="bnReprocessJob" alignment="0" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="bnCancelJob" linkSize="1" max="32767" attributes="0"/>
|
||||
<Component id="bnShowProgress" linkSize="1" max="32767" attributes="0"/>
|
||||
<Component id="bnCancelModule" linkSize="1" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="bnReprocessJob" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="bnShowCaseLog" alignment="1" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
@@ -135,8 +136,6 @@
|
||||
<EmptySpace min="-2" pref="68" max="-2" attributes="0"/>
|
||||
<Component id="bnReprocessJob" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="bnDeleteCase" linkSize="2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="bnShowCaseLog" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
@@ -255,28 +254,6 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnCancelJobActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="bnDeleteCase">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestControlPanel.bnDeleteCase.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestControlPanel.bnDeleteCase.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[162, 23]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[162, 23]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[162, 23]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnDeleteCaseActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lbPending">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
|
||||
+9
-81
@@ -63,7 +63,6 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.CaseDeletionResult;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.JobsSnapshot;
|
||||
import org.sleuthkit.autopsy.guiutils.DurationCellRenderer;
|
||||
import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer;
|
||||
@@ -621,7 +620,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
}
|
||||
int row = completedTable.getSelectedRow();
|
||||
boolean enabled = row >= 0 && row < completedTable.getRowCount();
|
||||
bnDeleteCase.setEnabled(enabled);
|
||||
bnShowCaseLog.setEnabled(enabled);
|
||||
bnReprocessJob.setEnabled(enabled);
|
||||
});
|
||||
@@ -632,7 +630,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
*/
|
||||
private void initButtons() {
|
||||
bnOptions.setEnabled(true);
|
||||
bnDeleteCase.setEnabled(false);
|
||||
enablePrioritizeButtons(false);
|
||||
enableDeprioritizeButtons(false);
|
||||
bnShowCaseLog.setEnabled(false);
|
||||
@@ -1229,7 +1226,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
completedScrollPane = new javax.swing.JScrollPane();
|
||||
completedTable = new javax.swing.JTable();
|
||||
bnCancelJob = new javax.swing.JButton();
|
||||
bnDeleteCase = new javax.swing.JButton();
|
||||
lbPending = new javax.swing.JLabel();
|
||||
lbRunning = new javax.swing.JLabel();
|
||||
lbCompleted = new javax.swing.JLabel();
|
||||
@@ -1317,17 +1313,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(bnDeleteCase, org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnDeleteCase.text")); // NOI18N
|
||||
bnDeleteCase.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnDeleteCase.toolTipText")); // NOI18N
|
||||
bnDeleteCase.setMaximumSize(new java.awt.Dimension(162, 23));
|
||||
bnDeleteCase.setMinimumSize(new java.awt.Dimension(162, 23));
|
||||
bnDeleteCase.setPreferredSize(new java.awt.Dimension(162, 23));
|
||||
bnDeleteCase.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
bnDeleteCaseActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(lbPending, org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.lbPending.text")); // NOI18N
|
||||
|
||||
@@ -1547,13 +1532,13 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
.addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1021, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1021, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnDeleteCase, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnShowCaseLog, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(bnCancelJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnShowProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnCancelModule, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(bnReprocessJob, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(bnShowCaseLog, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1021, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
@@ -1565,7 +1550,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnDeleteCase, bnShowProgress});
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnShowProgress});
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnClusterMetrics, bnExit, bnOpenLogDir, bnOptions, bnPause, bnRefresh});
|
||||
|
||||
@@ -1612,8 +1597,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
.addGap(68, 68, 68)
|
||||
.addComponent(bnReprocessJob, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(bnDeleteCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(bnShowCaseLog, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
@@ -1631,7 +1614,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnClusterMetrics, bnDeleteCase, bnExit, bnOpenLogDir, bnOptions, bnPrioritizeCase, bnPrioritizeJob, bnRefresh, bnShowProgress});
|
||||
layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {bnCancelJob, bnCancelModule, bnClusterMetrics, bnExit, bnOpenLogDir, bnOptions, bnPrioritizeCase, bnPrioritizeJob, bnRefresh, bnShowProgress});
|
||||
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@@ -1649,60 +1632,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}//GEN-LAST:event_bnRefreshActionPerformed
|
||||
|
||||
/**
|
||||
* Handles a click on the delete case button. If an entry is selected that
|
||||
* can be deleted, pops up a confirmation dialog. Upon confirmation, asks
|
||||
* AutoIngestManager to delete the entry and asks for an updated view.
|
||||
*
|
||||
* @param evt The button click event.
|
||||
*/
|
||||
@Messages({
|
||||
"AutoIngestControlPanel.DeletionFailed=Deletion failed for job"
|
||||
})
|
||||
private void bnDeleteCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDeleteCaseActionPerformed
|
||||
if (completedTable.getModel().getRowCount() < 0 || completedTable.getSelectedRow() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String caseName = (String) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.CASE.ordinal());
|
||||
Object[] options = {
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.Delete"),
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotDelete")
|
||||
};
|
||||
Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?"};
|
||||
int reply = JOptionPane.showOptionDialog(this,
|
||||
msgContent,
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmDeletionHeader"),
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[JOptionPane.NO_OPTION]);
|
||||
if (reply == JOptionPane.YES_OPTION) {
|
||||
bnDeleteCase.setEnabled(false);
|
||||
bnShowCaseLog.setEnabled(false);
|
||||
if (completedTable.getModel().getRowCount() > 0 && completedTable.getSelectedRow() >= 0) {
|
||||
Path caseDirectoryPath = (Path) completedTable.getModel().getValueAt(completedTable.convertRowIndexToModel(completedTable.getSelectedRow()), JobsTableModelColumns.CASE_DIRECTORY_PATH.ordinal());
|
||||
completedTable.clearSelection();
|
||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
CaseDeletionResult result = manager.deleteCase(caseName, caseDirectoryPath);
|
||||
refreshTables();
|
||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
if (CaseDeletionResult.FAILED == result) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
String.format("Could not delete case %s. It may be in use.", caseName),
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
} else if (CaseDeletionResult.PARTIALLY_DELETED == result) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
String.format("Could not fully delete case %s. See system log for details.", caseName),
|
||||
org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}//GEN-LAST:event_bnDeleteCaseActionPerformed
|
||||
|
||||
/**
|
||||
* Handles a click on the cancel auto ingest job button. Cancels the
|
||||
* selected job.
|
||||
@@ -1976,7 +1905,6 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
|
||||
private javax.swing.JButton bnCancelJob;
|
||||
private javax.swing.JButton bnCancelModule;
|
||||
private javax.swing.JButton bnClusterMetrics;
|
||||
private javax.swing.JButton bnDeleteCase;
|
||||
private javax.swing.JButton bnDeprioritizeCase;
|
||||
private javax.swing.JButton bnDeprioritizeJob;
|
||||
private javax.swing.JButton bnExit;
|
||||
|
||||
+22
-5
@@ -51,8 +51,10 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
final class AutoIngestDashboard extends JPanel implements Observer {
|
||||
|
||||
private final static String ADMIN_ACCESS_FILE_NAME = "_aiaa"; // NON-NLS
|
||||
private final static String ADMIN_ACCESS_FILE_NAME = "admin"; // NON-NLS
|
||||
private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_ACCESS_FILE_NAME).toString();
|
||||
private final static String ADMIN_EXT_ACCESS_FILE_NAME = "adminext"; // NON-NLS
|
||||
private final static String ADMIN_EXT_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_EXT_ACCESS_FILE_NAME).toString();
|
||||
private final static String AID_REFRESH_THREAD_NAME = "AID-refresh-jobs-%d";
|
||||
private final static int AID_REFRESH_INTERVAL_SECS = 30;
|
||||
private final static int AID_DELAY_BEFORE_FIRST_REFRESH = 0;
|
||||
@@ -277,8 +279,8 @@ final class AutoIngestDashboard extends JPanel implements Observer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the table models using a RefreshChildrenEvent and refreshes the JTables
|
||||
* that use the models.
|
||||
* Reloads the table models using a RefreshChildrenEvent and refreshes the
|
||||
* JTables that use the models.
|
||||
*
|
||||
*/
|
||||
void refreshTables() {
|
||||
@@ -318,9 +320,24 @@ final class AutoIngestDashboard extends JPanel implements Observer {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not system adminstrator features of the dashboard
|
||||
* are enabled.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
static boolean isAdminAutoIngestDashboard() {
|
||||
File f = new File(ADMIN_ACCESS_FILE_PATH);
|
||||
return f.exists();
|
||||
return new File(ADMIN_ACCESS_FILE_PATH).exists() || new File(ADMIN_EXT_ACCESS_FILE_PATH).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the extended system administrator features of the
|
||||
* cases dashboard are enabled.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
static boolean extendedFeaturesAreEnabled() {
|
||||
return new File(ADMIN_EXT_ACCESS_FILE_PATH).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.LogRecord;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
|
||||
/**
|
||||
* A logger for the auto ingest dashboard log.
|
||||
*/
|
||||
final class AutoIngestDashboardLogger {
|
||||
|
||||
private static final int LOG_SIZE = 50000000; // In bytes, zero is unlimited.
|
||||
private static final int LOG_FILE_COUNT = 10;
|
||||
private static final Logger logger = Logger.getLogger("AutoIngestDashboardLogger"); //NON-NLS
|
||||
private static final String NEWLINE = System.lineSeparator();
|
||||
@GuardedBy("AutoIngestDashboardLogger")
|
||||
private static boolean configured;
|
||||
|
||||
/**
|
||||
* Gets a logger for the auto ingest dashboard log.
|
||||
*
|
||||
* @return The logger.
|
||||
*/
|
||||
synchronized static Logger getLogger() {
|
||||
if (!configured) {
|
||||
Path logFilePath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "var", "log", "auto_ingest_dashboard.log"); //NON-NLS
|
||||
try {
|
||||
FileHandler fileHandler = new FileHandler(logFilePath.toString(), LOG_SIZE, LOG_FILE_COUNT);
|
||||
fileHandler.setEncoding(PlatformUtil.getLogFileEncoding());
|
||||
fileHandler.setFormatter(new Formatter() {
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
Throwable thrown = record.getThrown();
|
||||
String stackTrace = ""; //NON-NLS
|
||||
while (thrown != null) {
|
||||
stackTrace += thrown.toString() + NEWLINE;
|
||||
for (StackTraceElement traceElem : record.getThrown().getStackTrace()) {
|
||||
stackTrace += "\t" + traceElem.toString() + NEWLINE; //NON-NLS
|
||||
}
|
||||
thrown = thrown.getCause();
|
||||
}
|
||||
return (new Timestamp(record.getMillis())).toString() + " " //NON-NLS
|
||||
+ record.getSourceClassName() + " " //NON-NLS
|
||||
+ record.getSourceMethodName() + NEWLINE
|
||||
+ record.getLevel() + ": " //NON-NLS
|
||||
+ this.formatMessage(record) + NEWLINE
|
||||
+ stackTrace;
|
||||
}
|
||||
});
|
||||
logger.addHandler(fileHandler);
|
||||
logger.setUseParentHandlers(false);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS
|
||||
}
|
||||
configured = true;
|
||||
}
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents instantiation of this utility class.
|
||||
*/
|
||||
private AutoIngestDashboardLogger() {
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -77,7 +77,7 @@ public final class AutoIngestDashboardTopComponent extends TopComponent {
|
||||
AutoIngestDashboard dashboard = AutoIngestDashboard.createDashboard();
|
||||
tc.add(dashboard);
|
||||
dashboard.setSize(dashboard.getPreferredSize());
|
||||
//if the user has administrator access enabled open the Node Status top component as well
|
||||
//if the user has administrator access enabled open the Node Status and cases top components as well
|
||||
if (AutoIngestDashboard.isAdminAutoIngestDashboard()) {
|
||||
EventQueue.invokeLater(() -> {
|
||||
AinStatusDashboardTopComponent.openTopComponent(dashboard.getMonitor());
|
||||
|
||||
@@ -193,6 +193,7 @@ final class AutoIngestJob implements Comparable<AutoIngestJob>, IngestProgressSn
|
||||
this.ingestThreadsSnapshot = Collections.emptyList();
|
||||
this.ingestJobsSnapshot = Collections.emptyList();
|
||||
this.moduleRunTimesSnapshot = Collections.emptyMap();
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new AutoIngestJobException(String.format("Error creating automated ingest job"), ex);
|
||||
}
|
||||
@@ -651,7 +652,7 @@ final class AutoIngestJob implements Comparable<AutoIngestJob>, IngestProgressSn
|
||||
PENDING,
|
||||
PROCESSING,
|
||||
COMPLETED,
|
||||
DELETED
|
||||
DELETED // No longer used, retained for legacy jobs only.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -47,8 +47,8 @@ import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
||||
* of the error.
|
||||
*/
|
||||
@Immutable
|
||||
final class AutoIngestJobLogger {
|
||||
|
||||
final class AutoIngestJobLogger {
|
||||
|
||||
private static final String LOG_FILE_NAME = "auto_ingest_log.txt";
|
||||
private static final int LOCK_TIME_OUT = 15;
|
||||
private static final TimeUnit LOCK_TIME_OUT_UNIT = TimeUnit.MINUTES;
|
||||
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* Collects the auto ingest job node data stored in the manifest file
|
||||
* coordination service nodes.
|
||||
*/
|
||||
final class AutoIngestJobNodeDataCollector {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AutoIngestJobNodeDataCollector.class.getName());
|
||||
|
||||
static List<AutoIngestJobNodeData> getNodeData() throws CoordinationServiceException, InterruptedException {
|
||||
final CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
final List<String> nodePaths = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS);
|
||||
final List<AutoIngestJobNodeData> nodeDataList = new ArrayList<>();
|
||||
for (String nodePath : nodePaths) {
|
||||
try {
|
||||
final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, nodePath);
|
||||
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes);
|
||||
nodeDataList.add(nodeData);
|
||||
} catch (AutoIngestJobNodeData.InvalidDataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error reading node data from manifest file coordination service node %s", nodePath), ex); // NON-NLS
|
||||
}
|
||||
}
|
||||
return nodeDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents instantiation of this utility class.
|
||||
*/
|
||||
private AutoIngestJobNodeDataCollector() {
|
||||
}
|
||||
|
||||
}
|
||||
+1
-2
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Copyright 2018-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -372,7 +372,6 @@ final class AutoIngestJobsNode extends AbstractNode {
|
||||
break;
|
||||
case COMPLETED_JOB:
|
||||
actions.add(new AutoIngestAdminActions.ReprocessJobAction(jobWrapper.getJob()));
|
||||
actions.add(new AutoIngestAdminActions.DeleteCaseAction(jobWrapper.getJob()));
|
||||
actions.add(new AutoIngestAdminActions.ShowCaseLogAction(jobWrapper.getJob()));
|
||||
break;
|
||||
default:
|
||||
|
||||
+301
-401
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -72,13 +72,13 @@ final class AutoIngestMetricsCollector {
|
||||
switch (processingStatus) {
|
||||
case PENDING:
|
||||
case PROCESSING:
|
||||
case DELETED:
|
||||
/*
|
||||
* These are not jobs we care about for metrics, so
|
||||
* we will ignore them.
|
||||
*/
|
||||
break;
|
||||
case COMPLETED:
|
||||
case DELETED: // Assuming deleted jobs were completed before they were deleted.
|
||||
newMetricsSnapshot.addCompletedJobMetric(job.getCompletedDate(), job.getDataSourceSize());
|
||||
break;
|
||||
default:
|
||||
@@ -96,7 +96,7 @@ final class AutoIngestMetricsCollector {
|
||||
|
||||
return newMetricsSnapshot;
|
||||
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
} catch (CoordinationService.CoordinationServiceException | InterruptedException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to get node list from coordination service", ex);
|
||||
return new MetricsSnapshot();
|
||||
}
|
||||
|
||||
+4
-89
@@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
@@ -38,20 +37,15 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseActionException;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.StopWatch;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEventException;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus;
|
||||
import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.DELETED;
|
||||
import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PENDING;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.CaseDeletionResult;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType;
|
||||
|
||||
@@ -361,6 +355,9 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
|
||||
newJobsSnapshot.addOrReplaceCompletedJob(job);
|
||||
break;
|
||||
case DELETED:
|
||||
/*
|
||||
* Ignore jobs marked as deleted.
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
LOGGER.log(Level.SEVERE, "Unknown AutoIngestJobData.ProcessingStatus");
|
||||
@@ -378,7 +375,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
|
||||
|
||||
return newJobsSnapshot;
|
||||
|
||||
} catch (CoordinationServiceException ex) {
|
||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to get node list from coordination service", ex);
|
||||
return new JobsSnapshot();
|
||||
}
|
||||
@@ -659,88 +656,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a case. This includes deleting the case directory, the text
|
||||
* index, and the case database. This does not include the directories
|
||||
* containing the data sources and their manifests.
|
||||
*
|
||||
* @param job The job whose case you want to delete
|
||||
*
|
||||
* @return A result code indicating success, partial success, or failure.
|
||||
*/
|
||||
CaseDeletionResult deleteCase(AutoIngestJob job) {
|
||||
String caseName = job.getManifest().getCaseName();
|
||||
Path caseDirectoryPath = job.getCaseDirectoryPath();
|
||||
Path metadataFilePath = caseDirectoryPath.resolve(caseName + CaseMetadata.getFileExtension());
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
synchronized (jobsLock) {
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to acquire jobsLock (Java monitor in AutoIngestMonitor class) for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
try {
|
||||
CaseMetadata metadata = new CaseMetadata(metadataFilePath);
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to read case metadata for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
Case.deleteCase(metadata);
|
||||
} catch (CaseMetadata.CaseMetadataException ex) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to read case metadata file %s for case %s at %s", metadataFilePath, caseName, caseDirectoryPath), ex);
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to fail to read case metadata file %s for case %s at %s", stopWatch.getElapsedTimeSecs(), metadataFilePath, caseName, caseDirectoryPath));
|
||||
return CaseDeletionResult.FAILED;
|
||||
} catch (CaseActionException ex) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to delete case %s at %s", caseName, caseDirectoryPath), ex);
|
||||
return CaseDeletionResult.FAILED;
|
||||
}
|
||||
|
||||
// Update the state of completed jobs associated with this case to indicate
|
||||
// that the case has been deleted
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
List<AutoIngestJob> completedJobs = getCompletedJobs();
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to get completed jobs listing for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
for (AutoIngestJob completedJob : completedJobs) {
|
||||
if (caseName.equals(completedJob.getManifest().getCaseName())) {
|
||||
try {
|
||||
completedJob.setProcessingStatus(DELETED);
|
||||
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(completedJob);
|
||||
coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, completedJob.getManifest().getFilePath().toString(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to update completed job node data for %s when deleting case %s at %s", completedJob.getManifest().getFilePath(), caseName, caseDirectoryPath), ex);
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to fail to update job node data for completed jobs for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
return CaseDeletionResult.PARTIALLY_DELETED;
|
||||
}
|
||||
}
|
||||
}
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to update job node data for completed jobs for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
|
||||
// Remove jobs associated with this case from the completed jobs collection.
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
completedJobs.removeIf((AutoIngestJob completedJob)
|
||||
-> completedJob.getManifest().getCaseName().equals(caseName));
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to remove completed jobs for case %s at %s from current jobs snapshot", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
|
||||
// Publish a message to update auto ingest nodes.
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty()));
|
||||
stopWatch.stop();
|
||||
LOGGER.log(Level.INFO, String.format("Used %d s to publish job deletion event for case %s at %s", stopWatch.getElapsedTimeSecs(), caseName, caseDirectoryPath));
|
||||
}
|
||||
|
||||
return CaseDeletionResult.FULLY_DELETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given control event to the given node.
|
||||
*
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.sleuthkit.autopsy.progress.AppFrameProgressBar;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
import org.sleuthkit.autopsy.progress.TaskCancellable;
|
||||
|
||||
/**
|
||||
* A base class for action classes that kick off a cancellable task that runs in
|
||||
* a background thread and reports progress using an application frame progress
|
||||
* bar.
|
||||
*/
|
||||
abstract class BackgroundTaskAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String progressDisplayName;
|
||||
|
||||
/**
|
||||
* Constructs the base class part of action classes that kick off a
|
||||
* cancellable task that runs in a background thread and reports progress
|
||||
* using an application frame progress bar.
|
||||
*
|
||||
* @param actionName The name of the action.
|
||||
* @param progressDisplayName The display name for the progress bar.
|
||||
*/
|
||||
BackgroundTaskAction(String actionName, String progressDisplayName) {
|
||||
super(actionName);
|
||||
this.progressDisplayName = progressDisplayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
final AppFrameProgressBar progress = new AppFrameProgressBar(progressDisplayName);
|
||||
final TaskCancellable taskCanceller = new TaskCancellable(progress);
|
||||
progress.setCancellationBehavior(taskCanceller);
|
||||
final Runnable task = getTask(progress);
|
||||
final FutureTask<Void> future = new FutureTask<>(task, null);
|
||||
taskCanceller.setFuture(future);
|
||||
new Thread(future).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the background task to be executed. The task is expected to report
|
||||
* its progress using the supplied progress indicator and to check for
|
||||
* cancellation by checking to see if the thread it is running in has been
|
||||
* interrupted.
|
||||
*
|
||||
* @param progress A progress indicator for the task.
|
||||
*
|
||||
* @return The Runnnable task.
|
||||
*/
|
||||
abstract Runnable getTask(ProgressIndicator progress);
|
||||
|
||||
@Override
|
||||
public BackgroundTaskAction clone() throws CloneNotSupportedException {
|
||||
super.clone();
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -228,7 +228,6 @@ AutoIngestControlPanel.bnShowProgress.text=Ingest Progress
|
||||
AutoIngestControlPanel.bnCancelJob.text=&Cancel Job
|
||||
AutoIngestControlPanel.bnCancelModule.text=Cancel &Module
|
||||
AutoIngestControlPanel.bnReprocessJob.text=Reprocess Job
|
||||
AutoIngestControlPanel.bnDeleteCase.text=&Delete Case
|
||||
AutoIngestControlPanel.bnShowCaseLog.text=Show Case &Log
|
||||
AutoIngestControlPanel.bnPause.text=Pause
|
||||
AutoIngestControlPanel.bnRefresh.text=&Refresh
|
||||
@@ -255,3 +254,6 @@ AinStatusDashboard.clusterMetricsButton.text=Auto Ingest &Metrics
|
||||
AinStatusDashboard.nodeStatusTableTitle.text=Auto Ingest Nodes
|
||||
AinStatusDashboard.healthMonitorButton.text=Health Monitor
|
||||
CasesDashboardTopComponent.refreshButton.text=Refresh
|
||||
AutoIngestCasesDeletionDialog.jLabel1.text=Progress
|
||||
CasesDashboardTopComponent.deleteOrphanCaseNodesButton.text=Delete Orphan Case Znodes
|
||||
CasesDashboardTopComponent.deleteOrphanManifestNodesButton.text=Delete Orphan Manifest Znodes
|
||||
|
||||
+55
-8
@@ -10,8 +10,6 @@ AinStatusNode.status.title=Status
|
||||
AinStatusNode.status.unknown=Unknown
|
||||
AutoIngestAdminActions.cancelJobAction.title=Cancel Job
|
||||
AutoIngestAdminActions.cancelModuleAction.title=Cancel Module
|
||||
AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case.
|
||||
AutoIngestAdminActions.deleteCaseAction.title=Delete Case
|
||||
AutoIngestAdminActions.pause.title=Pause Node
|
||||
AutoIngestAdminActions.progressDialogAction.title=Ingest Progress
|
||||
AutoIngestAdminActions.reprocessJobAction.error=Failed to reprocess job
|
||||
@@ -56,7 +54,6 @@ AutoIngestControlPanel.Cancelling=Cancelling...
|
||||
AutoIngestControlPanel.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already
|
||||
AutoIngestControlPanel.ConfigLocked=The shared configuration directory is locked because upload from another node is in progress. \nIf this is an error, you can unlock the directory and then retry the upload.
|
||||
AutoIngestControlPanel.ConfigLockedTitle=Configuration directory locked
|
||||
AutoIngestControlPanel.DeletionFailed=Deletion failed for job
|
||||
AutoIngestControlPanel.EnableConfigurationSettings=Enable shared configuration from the options panel before uploading
|
||||
AutoIngestControlPanel.errorMessage.caseDeprioritization=An error occurred when deprioritizing the case. Some or all jobs may not have been deprioritized.
|
||||
AutoIngestControlPanel.errorMessage.casePrioritization=An error occurred when prioritizing the case. Some or all jobs may not have been prioritized.
|
||||
@@ -167,9 +164,58 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard
|
||||
CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs
|
||||
CTL_CasesDashboardAction=Multi-User Cases Dashboard
|
||||
CTL_CasesDashboardTopComponent=Cases
|
||||
DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories
|
||||
DeleteCasesAction.menuItemText=Delete Case and Jobs
|
||||
DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing
|
||||
DeleteCaseAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes
|
||||
DeleteCaseAction.menuItemText=Delete Case(s)
|
||||
DeleteCaseAction.progressDisplayName=Delete Case(s)
|
||||
DeleteCaseAction.taskName=app-input-and-output
|
||||
DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n
|
||||
DeleteCaseInputAction.menuItemText=Delete Input
|
||||
DeleteCaseInputAction.progressDisplayName=Delete Input
|
||||
DeleteCaseInputAction.taskName=input
|
||||
DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes
|
||||
DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output
|
||||
DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output
|
||||
DeleteCaseInputAndOutputAction.taskName=input-and-output
|
||||
DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes
|
||||
DeleteCaseOutputAction.menuItemText=Delete Output
|
||||
DeleteCaseOutputAction.progressDisplayName=Delete Output
|
||||
DeleteCaseOutputAction.taskName=output
|
||||
DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...
|
||||
DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...
|
||||
DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...
|
||||
DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...
|
||||
DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...
|
||||
DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode...
|
||||
# {0} - data source path
|
||||
DeleteCaseTask.progress.deletingDataSource=Deleting data source {0}...
|
||||
DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}...
|
||||
DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode...
|
||||
DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}...
|
||||
DeleteCaseTask.progress.openingCaseDatabase=Opening the case database...
|
||||
DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}...
|
||||
DeleteCaseTask.progress.startMessage=Starting deletion...
|
||||
DeleteOrphanCaseNodesAction.progressDisplayName=Cleanup Case Znodes
|
||||
DeleteOrphanCaseNodesTask.progress.connectingToCoordSvc=Connecting to the coordination service
|
||||
# {0} - node path
|
||||
DeleteOrphanCaseNodesTask.progress.deletingOrphanedCaseNode=Deleting orphaned case znode {0}
|
||||
DeleteOrphanCaseNodesTask.progress.gettingCaseNodesListing=Querying coordination service for case znodes
|
||||
DeleteOrphanCaseNodesTask.progress.startMessage=Starting orphaned case znode cleanup
|
||||
DeleteOrphanManifestNodesAction.progressDisplayName=Cleanup Manifest File Znodes
|
||||
DeleteOrphanManifestNodesTask.progress.connectingToCoordSvc=Connecting to the coordination service
|
||||
# {0} - node path
|
||||
DeleteOrphanManifestNodesTask.progress.deletingOrphanedManifestNode=Deleting orphaned manifest file znode {0}
|
||||
DeleteOrphanManifestNodesTask.progress.gettingManifestNodes=Querying the coordination service for manifest file znodes
|
||||
DeleteOrphanManifestNodesTask.progress.startMessage=Starting orphaned manifest file znode cleanup
|
||||
HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases
|
||||
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.
|
||||
OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.
|
||||
@@ -286,7 +332,6 @@ PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job "%s".
|
||||
PrioritizationAction.prioritizeJobAction.title=Prioritize Job
|
||||
PrioritizedIconCellRenderer.notPrioritized.tooltiptext=This job has not been prioritized.
|
||||
PrioritizedIconCellRenderer.prioritized.tooltiptext=This job has been prioritized. The most recently prioritized job should be processed next.
|
||||
ShowCaseDeletionStatusAction.menuItemText=Show Deletion Status
|
||||
SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping
|
||||
SingleUserCaseImporter.WillImport=Will import:
|
||||
SingleUserCaseImporter.None=None
|
||||
@@ -380,7 +425,6 @@ AutoIngestControlPanel.bnShowProgress.text=Ingest Progress
|
||||
AutoIngestControlPanel.bnCancelJob.text=&Cancel Job
|
||||
AutoIngestControlPanel.bnCancelModule.text=Cancel &Module
|
||||
AutoIngestControlPanel.bnReprocessJob.text=Reprocess Job
|
||||
AutoIngestControlPanel.bnDeleteCase.text=&Delete Case
|
||||
AutoIngestControlPanel.bnShowCaseLog.text=Show Case &Log
|
||||
AutoIngestControlPanel.bnPause.text=Pause
|
||||
AutoIngestControlPanel.bnRefresh.text=&Refresh
|
||||
@@ -407,3 +451,6 @@ AinStatusDashboard.clusterMetricsButton.text=Auto Ingest &Metrics
|
||||
AinStatusDashboard.nodeStatusTableTitle.text=Auto Ingest Nodes
|
||||
AinStatusDashboard.healthMonitorButton.text=Health Monitor
|
||||
CasesDashboardTopComponent.refreshButton.text=Refresh
|
||||
AutoIngestCasesDeletionDialog.jLabel1.text=Progress
|
||||
CasesDashboardTopComponent.deleteOrphanCaseNodesButton.text=Delete Orphan Case Znodes
|
||||
CasesDashboardTopComponent.deleteOrphanManifestNodesButton.text=Delete Orphan Manifest Znodes
|
||||
|
||||
+25
-10
@@ -31,23 +31,28 @@ import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrows
|
||||
*/
|
||||
final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer {
|
||||
|
||||
private final DeleteCaseInputDirectoriesAction deleteCaseInputAction;
|
||||
private final DeleteCasesForReprocessingAction deleteCaseOutputAction;
|
||||
private final DeleteCasesAction deleteCaseAction;
|
||||
private final DeleteCaseAction deleteCaseAction;
|
||||
private final DeleteCaseInputAction deleteCaseInputAction;
|
||||
private final DeleteCaseOutputAction deleteCaseOutputAction;
|
||||
private final DeleteCaseInputAndOutputAction deleteCaseInputAndOutputAction;
|
||||
|
||||
/**
|
||||
* Constructs a customizer for the multi-user case browser panel used in the
|
||||
* administrative dashboard for auto ingest cases to present a tabular view
|
||||
* of the multi-user cases known to the coordination service.
|
||||
*
|
||||
* @param executor An executor for tasks for actions that do work in the
|
||||
* background.
|
||||
*/
|
||||
CasesDashboardCustomizer() {
|
||||
/*
|
||||
* These actions are shared by all nodes in order to support multiple
|
||||
* selection.
|
||||
*/
|
||||
deleteCaseInputAction = new DeleteCaseInputDirectoriesAction();
|
||||
deleteCaseOutputAction = new DeleteCasesForReprocessingAction();
|
||||
deleteCaseAction = new DeleteCasesAction();
|
||||
deleteCaseAction = new DeleteCaseAction();
|
||||
deleteCaseInputAction = new DeleteCaseInputAction();
|
||||
deleteCaseOutputAction = new DeleteCaseOutputAction();
|
||||
deleteCaseInputAndOutputAction = new DeleteCaseInputAndOutputAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,6 +61,13 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer {
|
||||
properties.add(Column.CREATE_DATE);
|
||||
properties.add(Column.LAST_ACCESS_DATE);
|
||||
properties.add(Column.DIRECTORY);
|
||||
properties.add(Column.MANIFEST_FILE_ZNODES_DELETE_STATUS);
|
||||
if (AutoIngestDashboard.extendedFeaturesAreEnabled()) {
|
||||
properties.add(Column.DATA_SOURCES_DELETE_STATUS);
|
||||
}
|
||||
properties.add(Column.TEXT_INDEX_DELETE_STATUS);
|
||||
properties.add(Column.CASE_DB_DELETE_STATUS);
|
||||
properties.add(Column.CASE_DIR_DELETE_STATUS);
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -76,10 +88,13 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer {
|
||||
List<Action> actions = new ArrayList<>();
|
||||
actions.add(new OpenCaseAction(nodeData));
|
||||
actions.add(new OpenAutoIngestLogAction(nodeData));
|
||||
actions.add(deleteCaseInputAction);
|
||||
actions.add(deleteCaseOutputAction);
|
||||
actions.add(deleteCaseAction);
|
||||
actions.add(new ShowCaseDeletionStatusAction(nodeData));
|
||||
if (AutoIngestDashboard.extendedFeaturesAreEnabled()) {
|
||||
actions.add(deleteCaseInputAction);
|
||||
actions.add(deleteCaseOutputAction);
|
||||
actions.add(deleteCaseInputAndOutputAction);
|
||||
} else {
|
||||
actions.add(deleteCaseAction);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
||||
+31
-3
@@ -20,8 +20,12 @@
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="refreshButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="458" max="32767" attributes="0"/>
|
||||
<Component id="refreshButton" linkSize="3" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="deleteOrphanCaseNodesButton" linkSize="3" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="deleteOrphanManifestNodesButton" linkSize="3" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="caseBrowserScrollPane" max="32767" attributes="0"/>
|
||||
@@ -37,7 +41,11 @@
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="caseBrowserScrollPane" pref="246" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="refreshButton" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="refreshButton" linkSize="2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="deleteOrphanCaseNodesButton" linkSize="2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="deleteOrphanManifestNodesButton" linkSize="2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
@@ -58,5 +66,25 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="deleteOrphanCaseNodesButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="CasesDashboardTopComponent.deleteOrphanCaseNodesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteOrphanCaseNodesButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="deleteOrphanManifestNodesButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="CasesDashboardTopComponent.deleteOrphanManifestNodesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteOrphanManifestNodesButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
||||
+42
-5
@@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
*/
|
||||
@TopComponent.Description(
|
||||
preferredID = "CasesDashboardTopComponent",
|
||||
persistenceType = TopComponent.PERSISTENCE_ALWAYS
|
||||
persistenceType = TopComponent.PERSISTENCE_NEVER
|
||||
)
|
||||
@TopComponent.Registration(
|
||||
mode = "dashboard",
|
||||
@@ -62,8 +62,6 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex
|
||||
* for multi-user cases. The top component is docked into the "dashboard
|
||||
* mode" defined by the auto ingest jobs top component.
|
||||
*/
|
||||
// RJCTODO: Consider moving all of the dashboard code into its own
|
||||
// admindashboards or dashboards package.
|
||||
public static void openTopComponent() {
|
||||
CasesDashboardTopComponent topComponent = (CasesDashboardTopComponent) WindowManager.getDefault().findTopComponent("CasesDashboardTopComponent"); // NON-NLS
|
||||
if (topComponent == null) {
|
||||
@@ -121,6 +119,8 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex
|
||||
|
||||
refreshButton = new javax.swing.JButton();
|
||||
caseBrowserScrollPane = new javax.swing.JScrollPane();
|
||||
deleteOrphanCaseNodesButton = new javax.swing.JButton();
|
||||
deleteOrphanManifestNodesButton = new javax.swing.JButton();
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(CasesDashboardTopComponent.class, "CasesDashboardTopComponent.refreshButton.text")); // NOI18N
|
||||
refreshButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
@@ -129,6 +129,20 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deleteOrphanCaseNodesButton, org.openide.util.NbBundle.getMessage(CasesDashboardTopComponent.class, "CasesDashboardTopComponent.deleteOrphanCaseNodesButton.text")); // NOI18N
|
||||
deleteOrphanCaseNodesButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
deleteOrphanCaseNodesButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deleteOrphanManifestNodesButton, org.openide.util.NbBundle.getMessage(CasesDashboardTopComponent.class, "CasesDashboardTopComponent.deleteOrphanManifestNodesButton.text")); // NOI18N
|
||||
deleteOrphanManifestNodesButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
deleteOrphanManifestNodesButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
@@ -138,28 +152,51 @@ public final class CasesDashboardTopComponent extends TopComponent implements Ex
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(refreshButton)
|
||||
.addGap(0, 458, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(deleteOrphanCaseNodesButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(deleteOrphanManifestNodesButton)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(caseBrowserScrollPane)
|
||||
.addContainerGap())))
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteOrphanCaseNodesButton, deleteOrphanManifestNodesButton, refreshButton});
|
||||
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(caseBrowserScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(refreshButton)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(refreshButton)
|
||||
.addComponent(deleteOrphanCaseNodesButton)
|
||||
.addComponent(deleteOrphanManifestNodesButton))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {deleteOrphanCaseNodesButton, deleteOrphanManifestNodesButton, refreshButton});
|
||||
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
|
||||
caseBrowserPanel.displayCases();
|
||||
}//GEN-LAST:event_refreshButtonActionPerformed
|
||||
|
||||
private void deleteOrphanCaseNodesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteOrphanCaseNodesButtonActionPerformed
|
||||
new DeleteOrphanCaseNodesAction().actionPerformed(evt);
|
||||
}//GEN-LAST:event_deleteOrphanCaseNodesButtonActionPerformed
|
||||
|
||||
private void deleteOrphanManifestNodesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteOrphanManifestNodesButtonActionPerformed
|
||||
new DeleteOrphanManifestNodesAction().actionPerformed(evt);
|
||||
}//GEN-LAST:event_deleteOrphanManifestNodesButtonActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JScrollPane caseBrowserScrollPane;
|
||||
private javax.swing.JButton deleteOrphanCaseNodesButton;
|
||||
private javax.swing.JButton deleteOrphanManifestNodesButton;
|
||||
private javax.swing.JButton refreshButton;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An action that completely deletes one or more multi-user cases. Only the
|
||||
* components created by the application are deleted: the case output and the
|
||||
* coordination service nodes. Note that manifest file coordination service
|
||||
* nodes are only marked as deleted by setting the processing status field for
|
||||
* the corresponding auto ingest job to DELETED. This is done to avoid imposing
|
||||
* the requirement that the manifests be deleted before deleting the cases,
|
||||
* since at this time manifests are not considered to be case components created
|
||||
* by the application.
|
||||
*/
|
||||
final class DeleteCaseAction extends DeleteCaseComponentsAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs action that completely deletes one or more multi-user cases.
|
||||
* Only the components created by the application are deleted: the case
|
||||
* output and the coordination service nodes. Note that manifest file
|
||||
* coordination service nodes are only marked as deleted by setting the
|
||||
* processing status field for the corresponding auto ingest job to DELETED.
|
||||
* This is done to avoid imposing the requirement that the manifests be
|
||||
* deleted before deleting the cases, since at this time manifests are not
|
||||
* considered to be case components created by the application.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseAction.menuItemText=Delete Case(s)",
|
||||
"DeleteCaseAction.progressDisplayName=Delete Case(s)",
|
||||
"DeleteCaseAction.taskName=app-input-and-output"
|
||||
})
|
||||
DeleteCaseAction() {
|
||||
super(Bundle.DeleteCaseAction_menuItemText(), Bundle.DeleteCaseAction_progressDisplayName(), Bundle.DeleteCaseAction_taskName());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes"
|
||||
})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseAction_confirmationText())) {
|
||||
super.actionPerformed(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||
return new DeleteCaseTask(caseNodeData, DeleteCaseTask.DeleteOptions.DELETE_CASE, progress);
|
||||
}
|
||||
}
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.progress.AppFrameProgressBar;
|
||||
import org.sleuthkit.autopsy.progress.TaskCancellable;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An abstract class for an action that deletes components one or more
|
||||
* multi-user cases using a thread pool, one task per case. Uses the Template
|
||||
* Method design pattern to allow subclasses to specify the deletion task to be
|
||||
* performed.
|
||||
*
|
||||
* This cases to delete are discovered by querying the actions global context
|
||||
* lookup for CaseNodeData objects. See
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-1.html and
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-2.html for details.
|
||||
*/
|
||||
abstract class DeleteCaseComponentsAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int NUMBER_OF_THREADS = 4;
|
||||
private static final String THREAD_NAME_SUFFIX = "-task-%d"; //NON-NLS
|
||||
private static final String PROGRESS_DISPLAY_NAME = "%s for %s"; //NON-NLS
|
||||
private final String taskDisplayName;
|
||||
private final ExecutorService executor;
|
||||
|
||||
/**
|
||||
* Constructs an abstract class for an action that deletes components of one
|
||||
* or more multi-user cases using a thread pool, one task per case. Uses the
|
||||
* Template Method design pattern to allow subclasses to specify the
|
||||
* deletion task to be performed.
|
||||
*
|
||||
* @param menuItemText The menu item text for the action.
|
||||
* @param taskDisplayName The task display name for the progress indicator
|
||||
* for the task, to be inserted in the first position
|
||||
* of "%s for %s", where the second substitution is
|
||||
* the case name.
|
||||
* @param taskName The task name, to be inserted in the first
|
||||
* position of "%s-task-%d", where the second
|
||||
* substitution is the pool thread number.
|
||||
*/
|
||||
DeleteCaseComponentsAction(String menuItemText, String taskDisplayName, String taskName) {
|
||||
super(menuItemText);
|
||||
this.taskDisplayName = taskDisplayName;
|
||||
String threadNameFormat = taskName + THREAD_NAME_SUFFIX;
|
||||
executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS, new ThreadFactoryBuilder().setNameFormat(threadNameFormat).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
Collection<CaseNodeData> selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class));
|
||||
for (CaseNodeData nodeData : selectedNodeData) {
|
||||
AppFrameProgressBar progress = new AppFrameProgressBar(String.format(PROGRESS_DISPLAY_NAME, taskDisplayName, nodeData.getDisplayName()));
|
||||
TaskCancellable taskCanceller = new TaskCancellable(progress);
|
||||
progress.setCancellationBehavior(taskCanceller);
|
||||
Future<?> future = executor.submit(getTask(nodeData, progress));
|
||||
taskCanceller.setFuture(future);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the Template Method design pattern to allow subclasses to specify
|
||||
* the deletion task to be performed in a worker thread by this action.
|
||||
*
|
||||
* @param caseNodeData The case directory lock coordination service node
|
||||
* data for the case to be deleted.
|
||||
* @param progress A progress indicator for the task.
|
||||
*
|
||||
* @return A case deletion task, ready to be executed.
|
||||
*/
|
||||
abstract DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress);
|
||||
|
||||
@Override
|
||||
public DeleteCaseComponentsAction clone() throws CloneNotSupportedException {
|
||||
super.clone();
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
+17
-23
@@ -19,12 +19,11 @@
|
||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An action that deletes the auto ingest job input directories associated with
|
||||
@@ -32,13 +31,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
* ingest jobs are not deleted. This supports the use case where the directories
|
||||
* may need to be directed to reclaim space, but the option to restore the
|
||||
* directories without having the jobs be reprocessed is retained.
|
||||
*
|
||||
* This cases to delete are discovered by querying the actions global context
|
||||
* lookup for CaseNodeData objects. See
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-1.html and
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-2.html for details.
|
||||
*/
|
||||
final class DeleteCaseInputDirectoriesAction extends AbstractAction {
|
||||
final class DeleteCaseInputAction extends DeleteCaseComponentsAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -51,27 +45,27 @@ final class DeleteCaseInputDirectoriesAction extends AbstractAction {
|
||||
* reprocessed is retained.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories"
|
||||
"DeleteCaseInputAction.menuItemText=Delete Input",
|
||||
"DeleteCaseInputAction.progressDisplayName=Delete Input",
|
||||
"DeleteCaseInputAction.taskName=input"
|
||||
})
|
||||
DeleteCaseInputDirectoriesAction() {
|
||||
super(Bundle.DeleteCaseInputDirectoriesAction_menuItemText());
|
||||
setEnabled(false); // RJCTODO: Enable when implemented
|
||||
DeleteCaseInputAction() {
|
||||
super(Bundle.DeleteCaseInputAction_menuItemText(), Bundle.DeleteCaseInputAction_progressDisplayName(), Bundle.DeleteCaseInputAction_taskName());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n"
|
||||
})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
final Collection<CaseNodeData> selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class));
|
||||
// if (!selectedNodeData.isEmpty()) {
|
||||
// /*
|
||||
// * RJCTODO: Create a background task that does the deletion and
|
||||
// * displays results in a dialog with a scrolling text pane.
|
||||
// */
|
||||
// }
|
||||
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseInputAction_confirmationText())) {
|
||||
super.actionPerformed(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteCaseInputDirectoriesAction clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress);
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An action that completely deletes one or more multi-user cases, including any
|
||||
* associated auto ingest job input directories and all coordination service
|
||||
* nodes.
|
||||
*/
|
||||
final class DeleteCaseInputAndOutputAction extends DeleteCaseComponentsAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an action that completely deletes one or more multi-user
|
||||
* cases, including any associated auto ingest job input directories and
|
||||
* coordination service nodes.
|
||||
*/
|
||||
@Messages({
|
||||
"DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output",
|
||||
"DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output",
|
||||
"DeleteCaseInputAndOutputAction.taskName=input-and-output"
|
||||
})
|
||||
DeleteCaseInputAndOutputAction() {
|
||||
super(Bundle.DeleteCaseInputAndOutputAction_menuItemText(), Bundle.DeleteCaseInputAndOutputAction_progressDisplayName(), Bundle.DeleteCaseInputAndOutputAction_taskName());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest files\n\tData sources\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes"
|
||||
})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseInputAndOutputAction_confirmationText())) {
|
||||
super.actionPerformed(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT_AND_OUTPUT, progress);
|
||||
}
|
||||
|
||||
}
|
||||
+19
-25
@@ -19,12 +19,11 @@
|
||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An action that deletes everything except the auto ingest job input
|
||||
@@ -32,13 +31,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
* where a case needs to be reprocessed, so the input directories are not
|
||||
* deleted even though the coordination service nodes for the auto ingest jobs
|
||||
* are deleted.
|
||||
*
|
||||
* This cases to delete are discovered by querying the actions global context
|
||||
* lookup for CaseNodeData objects. See
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-1.html and
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-2.html for details.
|
||||
*/
|
||||
final class DeleteCasesForReprocessingAction extends AbstractAction {
|
||||
final class DeleteCaseOutputAction extends DeleteCaseComponentsAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -50,27 +44,27 @@ final class DeleteCasesForReprocessingAction extends AbstractAction {
|
||||
* ingest jobs are deleted.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing"
|
||||
"DeleteCaseOutputAction.menuItemText=Delete Output",
|
||||
"DeleteCaseOutputAction.progressDisplayName=Delete Output",
|
||||
"DeleteCaseOutputAction.taskName=output"
|
||||
})
|
||||
DeleteCasesForReprocessingAction() {
|
||||
super(Bundle.DeleteCasesForReprocessingAction_menuItemText());
|
||||
setEnabled(false); // RJCTODO: Enable when implemented
|
||||
DeleteCaseOutputAction() {
|
||||
super(Bundle.DeleteCaseOutputAction_menuItemText(), Bundle.DeleteCaseOutputAction_progressDisplayName(), Bundle.DeleteCaseOutputAction_taskName());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n\tManifest file znodes\n\tCase database\n\tCore.properties file\n\tCase directory\n\tCase znodes"
|
||||
})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
final Collection<CaseNodeData> selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class));
|
||||
// if (!selectedNodeData.isEmpty()) {
|
||||
// /*
|
||||
// * RJCTODO: Create a background task that does the deletion and
|
||||
// * displays results in a dialog with a scrolling text pane.
|
||||
// */
|
||||
// }
|
||||
}
|
||||
|
||||
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseOutputAction_confirmationText())) {
|
||||
super.actionPerformed(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteCasesForReprocessingAction clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress);
|
||||
}
|
||||
|
||||
}
|
||||
+945
@@ -0,0 +1,945 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.Lookup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.core.UserPreferencesException;
|
||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A task that deletes part or all of a given case. Note that all logging is
|
||||
* directed to the dedicated auto ingest dashboard log instead of to the general
|
||||
* application log.
|
||||
*/
|
||||
final class DeleteCaseTask implements Runnable {
|
||||
|
||||
private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 5;
|
||||
private static final int MANIFEST_DELETE_TRIES = 3;
|
||||
private static final Logger logger = AutoIngestDashboardLogger.getLogger();
|
||||
private final CaseNodeData caseNodeData;
|
||||
private final DeleteOptions deleteOption;
|
||||
private final ProgressIndicator progress;
|
||||
private final List<ManifestFileLock> manifestFileLocks;
|
||||
private CoordinationService coordinationService;
|
||||
private CaseMetadata caseMetadata;
|
||||
|
||||
/**
|
||||
* Options to support implementing different case deletion use cases.
|
||||
*/
|
||||
enum DeleteOptions {
|
||||
/**
|
||||
* Delete the auto ingest job manifests and corresponding data sources,
|
||||
* while leaving the manifest file coordination service nodes and the
|
||||
* rest of the case intact. The use case is freeing auto ingest input
|
||||
* directory space while retaining the option to restore the data
|
||||
* sources, effectively restoring the case.
|
||||
*/
|
||||
DELETE_INPUT,
|
||||
/**
|
||||
* Delete the manifest file coordination service nodes and the output
|
||||
* for a case, while leaving the auto ingest job manifests and
|
||||
* corresponding data sources intact. The use case is auto ingest
|
||||
* reprocessing of a case with a clean slate without having to restore
|
||||
* the manifests and data sources.
|
||||
*/
|
||||
DELETE_OUTPUT,
|
||||
/**
|
||||
* Delete everything.
|
||||
*/
|
||||
DELETE_INPUT_AND_OUTPUT,
|
||||
/**
|
||||
* Delete only the case components that the application created. This is
|
||||
* DELETE_OUTPUT with the additional feature that manifest file
|
||||
* coordination service nodes are marked as deleted, rather than
|
||||
* actually deleted. This eliminates the requirement that manifests and
|
||||
* data sources have to be deleted before deleting the case to avoid an
|
||||
* unwanted, automatic reprocessing of the case.
|
||||
*/
|
||||
DELETE_CASE
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a task that deletes part or all of a given case. Note that all
|
||||
* logging is directed to the dedicated auto ingest dashboard log instead of
|
||||
* to the general application log.
|
||||
*
|
||||
* @param caseNodeData The case directory coordination service node data for
|
||||
* the case.
|
||||
* @param deleteOption The deletion option for the task.
|
||||
* @param progress A progress indicator.
|
||||
*/
|
||||
DeleteCaseTask(CaseNodeData caseNodeData, DeleteOptions deleteOption, ProgressIndicator progress) {
|
||||
this.caseNodeData = caseNodeData;
|
||||
this.deleteOption = deleteOption;
|
||||
this.progress = progress;
|
||||
manifestFileLocks = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseTask.progress.startMessage=Starting deletion..."
|
||||
})
|
||||
public void run() {
|
||||
try {
|
||||
progress.start(Bundle.DeleteCaseTask_progress_startMessage());
|
||||
logger.log(Level.INFO, String.format("Starting deletion of %s (%s)", caseNodeData.getDisplayName(), deleteOption));
|
||||
deleteCase();
|
||||
logger.log(Level.INFO, String.format("Finished deletion of %s (%s)", caseNodeData.getDisplayName(), deleteOption));
|
||||
|
||||
} catch (CoordinationServiceException | IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
|
||||
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
} catch (Exception ex) {
|
||||
/*
|
||||
* This is an unexpected runtime exceptions firewall. It is here
|
||||
* because this task is designed to be able to be run in scenarios
|
||||
* where there is no call to get() on a Future<Void> associated with
|
||||
* the task, so this ensures that any such errors get logged.
|
||||
*/
|
||||
logger.log(Level.SEVERE, String.format("Unexpected error deleting %s", caseNodeData.getDisplayName()), ex);
|
||||
throw ex;
|
||||
|
||||
} finally {
|
||||
releaseManifestFileLocks();
|
||||
progress.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes part or all of the given case.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...",
|
||||
"DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...",
|
||||
"DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...",
|
||||
"DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths...",
|
||||
"DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...",
|
||||
"DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file...",
|
||||
"DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode...",
|
||||
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...",
|
||||
"DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...",
|
||||
"DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode..."
|
||||
})
|
||||
private void deleteCase() throws CoordinationServiceException, IOException, InterruptedException {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc());
|
||||
logger.log(Level.INFO, String.format("Connecting to the coordination service for deletion of %s", caseNodeData.getDisplayName()));
|
||||
coordinationService = CoordinationService.getInstance();
|
||||
checkForCancellation();
|
||||
|
||||
/*
|
||||
* Acquire an exclusive case name lock. The case name lock is the lock
|
||||
* that auto ingest node (AIN) job processing tasks acquire exclusively
|
||||
* when creating or opening a case specified in an auto ingest job
|
||||
* manifest file. The reason AINs do this is to ensure that only one of
|
||||
* them at a time can search the auto ingest output directory for an
|
||||
* existing case matching the one in the manifest file. If a matching
|
||||
* case is found, it is opened, otherwise the case is created. Acquiring
|
||||
* this lock effectively disables this AIN job processing task behavior
|
||||
* while the case is being deleted.
|
||||
*/
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock());
|
||||
logger.log(Level.INFO, String.format("Acquiring an exclusive case name lock for %s", caseNodeData.getDisplayName()));
|
||||
String caseNameLockName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory());
|
||||
try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) {
|
||||
if (nameLock == null) {
|
||||
logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
checkForCancellation();
|
||||
|
||||
/*
|
||||
* Acquire an exclusive case directory lock. A shared case directory
|
||||
* lock is acquired by each auto ingest node (AIN) and examiner node
|
||||
* (EIN) when it opens a case. The shared locks are held by the AINs
|
||||
* and EINs for as long as they have the case open. Acquiring this
|
||||
* lock exclusively ensures that no AIN or EIN has the case to be
|
||||
* deleted open and prevents another node from trying to open the
|
||||
* case while it is being deleted.
|
||||
*/
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock());
|
||||
logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName()));
|
||||
String caseDirLockName = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
|
||||
try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockName)) {
|
||||
if (caseDirLock == null) {
|
||||
logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
checkForCancellation();
|
||||
|
||||
/*
|
||||
* Acquire exclusive locks for the auto ingest job manifest
|
||||
* files for the case, if any. Manifest file locks are acquired
|
||||
* by the auto ingest node (AIN) input directory scanning tasks
|
||||
* when they look for auto ingest jobs to enqueue, and by the
|
||||
* AIN job execution tasks when they do a job. Acquiring these
|
||||
* locks here ensures that the scanning tasks and job execution
|
||||
* tasks cannot do anything with the auto ingest jobs for a case
|
||||
* during case deletion.
|
||||
*/
|
||||
if (!acquireManifestFileLocks()) {
|
||||
logger.log(Level.INFO, String.format("Could not delete %s because at least one manifest file lock was already held by another host", caseNodeData.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
checkForCancellation();
|
||||
|
||||
deleteCaseContents();
|
||||
checkForCancellation();
|
||||
|
||||
deleteCaseResourcesNode();
|
||||
checkForCancellation();
|
||||
|
||||
deleteCaseAutoIngestLogNode();
|
||||
checkForCancellation();
|
||||
|
||||
deleteManifestFileNodes();
|
||||
checkForCancellation();
|
||||
}
|
||||
|
||||
deleteCaseDirectoryNode();
|
||||
checkForCancellation();
|
||||
}
|
||||
|
||||
deleteCaseNameNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the manifest file paths for the case, if there are any.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error completing a
|
||||
* coordination service operation.
|
||||
* @throws InterruptedException If the thread in which this task is
|
||||
* running is interrupted while blocked
|
||||
* waiting for a coordination service
|
||||
* operation to complete.
|
||||
* @throws IOException If there is an error reading the
|
||||
* manifests list file.
|
||||
*/
|
||||
private List<Path> getManifestFilePaths() throws IOException, CoordinationServiceException, InterruptedException {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_gettingManifestPaths());
|
||||
logger.log(Level.INFO, String.format("Getting manifest file paths for %s", caseNodeData.getDisplayName()));
|
||||
final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName());
|
||||
final File manifestListsFile = manifestsListFilePath.toFile();
|
||||
if (manifestListsFile.exists()) {
|
||||
return getManifestPathsFromFile(manifestsListFilePath);
|
||||
} else {
|
||||
return getManifestPathsFromNodes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the manifest file paths for the case by reading them from
|
||||
* the manifests list file for the case.
|
||||
*
|
||||
* @param manifestsListFilePath The path of the manifests list file.
|
||||
*
|
||||
* @return A list of manifest file paths, possibly empty.
|
||||
*
|
||||
* @throws IOException If there is an error reading the manifests
|
||||
* list file.
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private List<Path> getManifestPathsFromFile(Path manifestsListFilePath) throws IOException, InterruptedException {
|
||||
final List<Path> manifestFilePaths = new ArrayList<>();
|
||||
try (final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath)) {
|
||||
while (manifestsListFileScanner.hasNextLine()) {
|
||||
checkForCancellation();
|
||||
final Path manifestFilePath = Paths.get(manifestsListFileScanner.nextLine());
|
||||
if (manifestFilePath.toFile().exists()) {
|
||||
manifestFilePaths.add(manifestFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return manifestFilePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the manifest file paths for the case by sifting through
|
||||
* the node data of the manifest file coordination service nodes and
|
||||
* matching on case name.
|
||||
*
|
||||
* @return A list of manifest file paths, possibly empty.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error completing a
|
||||
* coordination service operation.
|
||||
* @throws InterruptedException If the thread in which this task is
|
||||
* running is interrupted while blocked
|
||||
* waiting for a coordination service
|
||||
* operation to complete.
|
||||
*/
|
||||
private List<Path> getManifestPathsFromNodes() throws CoordinationServiceException, InterruptedException {
|
||||
/*
|
||||
* Get the original, undecorated case name from the case directory. This
|
||||
* is necessary because the case display name can be changed and the
|
||||
* original case name may have a time stamp added to make it unique,
|
||||
* depending on how the case was created. An alternative aproach would
|
||||
* be to strip off any time stamp from the case name in the case node
|
||||
* data.
|
||||
*/
|
||||
String caseName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory());
|
||||
final List<Path> manifestFilePaths = new ArrayList<>();
|
||||
final List<String> nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS);
|
||||
for (String manifestNodeName : nodeNames) {
|
||||
checkForCancellation();
|
||||
try {
|
||||
final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodeName);
|
||||
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes);
|
||||
if (caseName.equals(nodeData.getCaseName())) {
|
||||
Path manifestFilePath = nodeData.getManifestFilePath();
|
||||
if (manifestFilePath.toFile().exists()) {
|
||||
manifestFilePaths.add(manifestFilePath);
|
||||
}
|
||||
}
|
||||
} catch (CoordinationServiceException | InvalidDataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error getting coordination service node data from %s", manifestNodeName), ex);
|
||||
}
|
||||
}
|
||||
return manifestFilePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires either all or none of the auto ingest job manifest file locks
|
||||
* for a case.
|
||||
*
|
||||
* @return True if all of the locks were acquired; false otherwise.
|
||||
*
|
||||
* @throws CoordinationServiceException If there is an error completing a
|
||||
* coordination service operation.
|
||||
* @throws InterruptedException If the thread in which this task is
|
||||
* running is interrupted while blocked
|
||||
* waiting for a coordination service
|
||||
* operation to complete.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}..."
|
||||
})
|
||||
private boolean acquireManifestFileLocks() throws IOException, CoordinationServiceException, InterruptedException {
|
||||
boolean allLocksAcquired = true;
|
||||
List<Path> manifestFilePaths = getManifestFilePaths();
|
||||
logger.log(Level.INFO, String.format("Found %d manifest file path(s) for %s", manifestFilePaths.size(), caseNodeData.getDisplayName()));
|
||||
if (!manifestFilePaths.isEmpty()) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks());
|
||||
logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
||||
/*
|
||||
* When acquiring the locks, it is reasonable to block briefly,
|
||||
* since the auto ingest node (AIN) input directory scanning tasks
|
||||
* do a lot of short-term acquiring and releasing of the same locks.
|
||||
* The assumption here is that the originator of this case deletion
|
||||
* task is not asking for deletion of a case that has a job that an
|
||||
* auto ingest node (AIN) job execution task is working on and that
|
||||
* MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long anyway, so
|
||||
* waiting a bit should be fine.
|
||||
*/
|
||||
try {
|
||||
for (Path manifestPath : manifestFilePaths) {
|
||||
checkForCancellation();
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString()));
|
||||
logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName()));
|
||||
CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES);
|
||||
if (null != manifestLock) {
|
||||
manifestFileLocks.add(new ManifestFileLock(manifestPath, manifestLock));
|
||||
} else {
|
||||
logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName()));
|
||||
allLocksAcquired = false;
|
||||
releaseManifestFileLocks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||
releaseManifestFileLocks();
|
||||
throw ex;
|
||||
}
|
||||
} else {
|
||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES);
|
||||
}
|
||||
return allLocksAcquired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes case contents, based on the specified deletion option.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private void deleteCaseContents() throws InterruptedException {
|
||||
final File caseDirectory = caseNodeData.getDirectory().toFile();
|
||||
if (caseDirectory.exists()) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile());
|
||||
logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName()));
|
||||
Path caseMetadataPath = CaseMetadata.getCaseMetadataFilePath(caseNodeData.getDirectory());
|
||||
if (caseMetadataPath != null) {
|
||||
try {
|
||||
caseMetadata = new CaseMetadata(caseMetadataPath);
|
||||
checkForCancellation();
|
||||
if (!manifestFileLocks.isEmpty()) {
|
||||
if (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT) {
|
||||
deleteAutoIngestInput();
|
||||
} else if (deleteOption == DeleteOptions.DELETE_CASE) {
|
||||
markManifestFileNodesAsDeleted();
|
||||
}
|
||||
}
|
||||
checkForCancellation();
|
||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) {
|
||||
Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger);
|
||||
}
|
||||
|
||||
} catch (CaseMetadata.CaseMetadataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error reading metadata file for %s", caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.log(Level.WARNING, String.format("No case metadata file found for %s", caseNodeData.getDisplayName()));
|
||||
}
|
||||
|
||||
} else {
|
||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.CASE_DIR);
|
||||
logger.log(Level.INFO, String.format("No case directory found for %s", caseNodeData.getDisplayName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the auto ingest job input manifests for the case along with the
|
||||
* corresponding data sources.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseTask.progress.openingCaseDatabase=Opening the case database...",
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}..."
|
||||
})
|
||||
private void deleteAutoIngestInput() throws InterruptedException {
|
||||
SleuthkitCase caseDb = null;
|
||||
try {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase());
|
||||
logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName()));
|
||||
caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
|
||||
List<DataSource> dataSources = caseDb.getDataSources();
|
||||
checkForCancellation();
|
||||
|
||||
/*
|
||||
* For every manifest file associated with the case, attempt to
|
||||
* delete both the data source referenced by the manifest and the
|
||||
* manifest.
|
||||
*/
|
||||
boolean allInputDeleted = true;
|
||||
for (ManifestFileLock lock : manifestFileLocks) {
|
||||
checkForCancellation();
|
||||
Path manifestFilePath = lock.getManifestFilePath();
|
||||
final File manifestFile = manifestFilePath.toFile();
|
||||
if (manifestFile.exists()) {
|
||||
Manifest manifest = parseManifestFile(manifestFilePath);
|
||||
if (manifest != null) {
|
||||
if (deleteDataSources(manifest, dataSources) && deleteManifestFile(manifestFile)) {
|
||||
lock.setInputDeleted();
|
||||
} else {
|
||||
allInputDeleted = false;
|
||||
}
|
||||
} else {
|
||||
logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
allInputDeleted = false;
|
||||
}
|
||||
} else {
|
||||
logger.log(Level.WARNING, String.format("Did not find manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
}
|
||||
}
|
||||
if (allInputDeleted) {
|
||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES);
|
||||
}
|
||||
|
||||
} catch (TskCoreException | UserPreferencesException ex) {
|
||||
logger.log(Level.INFO, String.format("Failed to open or query the case database for %s", caseNodeData.getDisplayName()), ex);
|
||||
|
||||
} finally {
|
||||
if (caseDb != null) {
|
||||
caseDb.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a manifest file.
|
||||
*
|
||||
* @param manifestFilePath The manifest file path.
|
||||
*
|
||||
* @return A manifest, if the parsing is successful, null otherwise.
|
||||
*/
|
||||
private Manifest parseManifestFile(Path manifestFilePath) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_parsingManifest(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
Manifest manifest = null;
|
||||
for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) {
|
||||
if (parser.fileIsManifest(manifestFilePath)) {
|
||||
try {
|
||||
manifest = parser.parse(manifestFilePath);
|
||||
break;
|
||||
} catch (ManifestFileParser.ManifestFileParserException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a manifest file.
|
||||
*
|
||||
* @param manifestFile The manifest file.
|
||||
*
|
||||
* @return True if the file was deleted, false otherwise.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}..."
|
||||
})
|
||||
private boolean deleteManifestFile(File manifestFile) throws InterruptedException {
|
||||
/*
|
||||
* Delete the manifest file, allowing a few retries. This is a way to
|
||||
* resolve the race condition between this task and auto ingest node
|
||||
* (AIN) input directory scanning tasks, which parse manifests (actually
|
||||
* all files) before getting a coordination service lock, without
|
||||
* resorting to a protocol using locking of the input directory.
|
||||
*/
|
||||
Path manifestFilePath = manifestFile.toPath();
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
int tries = 0;
|
||||
boolean deleted = false;
|
||||
while (!deleted && tries < MANIFEST_DELETE_TRIES) {
|
||||
deleted = manifestFile.delete();
|
||||
if (!deleted) {
|
||||
++tries;
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
if (!deleted) {
|
||||
logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates and deletes the data source files referenced by a manifest.
|
||||
*
|
||||
* @param manifest A manifest.
|
||||
* @param dataSources The data sources in the case as obtained from the case
|
||||
* database.
|
||||
*
|
||||
* @return True if all of the data source files werre deleted, false
|
||||
* otherwise.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - data source path", "DeleteCaseTask.progress.deletingDataSource=Deleting data source {0}..."
|
||||
})
|
||||
private boolean deleteDataSources(Manifest manifest, List<DataSource> dataSources) {
|
||||
final Path dataSourcePath = manifest.getDataSourcePath();
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingDataSource(dataSourcePath));
|
||||
logger.log(Level.INFO, String.format("Deleting data source %s from %s", dataSourcePath, caseNodeData.getDisplayName()));
|
||||
|
||||
/*
|
||||
* There are two possibilities here. The data source may be an image,
|
||||
* and if so, it may be split into multiple files. In this case, all of
|
||||
* the files for the image need to be deleted. Otherwise, the data
|
||||
* source is a single directory or file (a logical file, logical file
|
||||
* set, report file, archive file, etc.). In this case, just the file
|
||||
* referenced by the manifest will be deleted.
|
||||
*/
|
||||
Set<Path> filesToDelete = new HashSet<>();
|
||||
int index = 0;
|
||||
while (index < dataSources.size() && filesToDelete.isEmpty()) {
|
||||
DataSource dataSource = dataSources.get(index);
|
||||
if (dataSource instanceof Image) {
|
||||
Image image = (Image) dataSource;
|
||||
String[] imageFilePaths = image.getPaths();
|
||||
/*
|
||||
* Check for a match between one of the paths for the image
|
||||
* files and the data source file path in the manifest.
|
||||
*/
|
||||
for (String imageFilePath : imageFilePaths) {
|
||||
Path candidatePath = Paths.get(imageFilePath);
|
||||
if (candidatePath.equals(dataSourcePath)) {
|
||||
/*
|
||||
* If a match is found, add all of the file paths for
|
||||
* the image to the set of files to be deleted.
|
||||
*/
|
||||
for (String path : imageFilePaths) {
|
||||
filesToDelete.add(Paths.get(path));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
/*
|
||||
* At a minimum, the data source at the file path given in the manifest
|
||||
* should be deleted. If the data source is not a disk image, this will
|
||||
* be the path of an archive, a logical file, or a logical directory.
|
||||
* TODO-4933: Currently, the contents extracted from an archive are not
|
||||
* deleted, nor are any additional files associated with a report data
|
||||
* source.
|
||||
*/
|
||||
filesToDelete.add(dataSourcePath);
|
||||
|
||||
/*
|
||||
* Delete the file(s).
|
||||
*/
|
||||
boolean allFilesDeleted = true;
|
||||
for (Path path : filesToDelete) {
|
||||
File fileOrDir = path.toFile();
|
||||
if (fileOrDir.exists() && !FileUtil.deleteFileDir(fileOrDir)) {
|
||||
allFilesDeleted = false;
|
||||
logger.log(Level.WARNING, String.format("Failed to delete data source file at %s for %s", path, caseNodeData.getDisplayName()));
|
||||
}
|
||||
}
|
||||
|
||||
return allFilesDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the manifest file coordination service nodes as deleted by setting
|
||||
* the auto ingest job processing status field to deleted.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private void markManifestFileNodesAsDeleted() throws InterruptedException {
|
||||
boolean allNodesMarked = true;
|
||||
for (ManifestFileLock manifestFileLock : manifestFileLocks) {
|
||||
String manifestFilePath = manifestFileLock.getManifestFilePath().toString();
|
||||
try {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Marking as deleted the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath);
|
||||
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes);
|
||||
nodeData.setProcessingStatus(AutoIngestJob.ProcessingStatus.DELETED);
|
||||
coordinationService.setNodeData(CategoryNode.MANIFESTS, manifestFilePath, nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InvalidDataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error marking as deleted the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||
allNodesMarked = false;
|
||||
}
|
||||
}
|
||||
if (allNodesMarked) {
|
||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the case resources coordination service node.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private void deleteCaseResourcesNode() throws InterruptedException {
|
||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode());
|
||||
logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName()));
|
||||
String resourcesNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
|
||||
try {
|
||||
coordinationService.deleteNode(CategoryNode.CASES, resourcesNodePath);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
if (!DeleteCaseUtils.isNoNodeException(ex)) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the case auto ingest log coordination service node.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private void deleteCaseAutoIngestLogNode() throws InterruptedException {
|
||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode());
|
||||
logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()));
|
||||
String logFilePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseNodeData.getDirectory());
|
||||
try {
|
||||
coordinationService.deleteNode(CategoryNode.CASES, logFilePath);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
if (!DeleteCaseUtils.isNoNodeException(ex)) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the case directory coordination service node if everything that
|
||||
* was supposed to be deleted was deleted. Otherwise, leave the node so that
|
||||
* what was and was not deleted can be inspected.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private void deleteCaseDirectoryNode() throws InterruptedException {
|
||||
if (((deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.DATA_SOURCES)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES))
|
||||
|| (deleteOption == DeleteOptions.DELETE_CASE
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)
|
||||
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES))) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode());
|
||||
logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName()));
|
||||
String caseDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
|
||||
try {
|
||||
coordinationService.deleteNode(CategoryNode.CASES, caseDirNodePath);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the case name coordiation service node.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
private void deleteCaseNameNode() throws InterruptedException {
|
||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode());
|
||||
logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName()));
|
||||
try {
|
||||
String caseNameLockNodeName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory());
|
||||
coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all of the manifest file locks that have been acquired by this
|
||||
* task.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}..."
|
||||
})
|
||||
private void releaseManifestFileLocks() {
|
||||
for (ManifestFileLock manifestFileLock : manifestFileLocks) {
|
||||
String manifestFilePath = manifestFileLock.getManifestFilePath().toString();
|
||||
try {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Releasing the exclusive coordination service lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
manifestFileLock.release();
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error releasing the exclusive coordination service lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
manifestFileLocks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all of the manifest file locks that have been acquired by this
|
||||
* task and attempts to delete the corresponding coordination service nodes.
|
||||
*
|
||||
* @return True if all of the manifest file coordianiton service nodes have
|
||||
* been deleted, false otherwise.
|
||||
*
|
||||
* @throws InterruptedException If the thread in which this task is running
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
@Messages({
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}..."
|
||||
})
|
||||
private void deleteManifestFileNodes() throws InterruptedException {
|
||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT) {
|
||||
boolean allINodesDeleted = true;
|
||||
Iterator<ManifestFileLock> iterator = manifestFileLocks.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ManifestFileLock manifestFileLock = iterator.next();
|
||||
String manifestFilePath = manifestFileLock.getManifestFilePath().toString();
|
||||
try {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Releasing the lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
manifestFileLock.release();
|
||||
if (manifestFileLock.isInputDeleted()) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath);
|
||||
} else {
|
||||
allINodesDeleted = false;
|
||||
}
|
||||
} catch (CoordinationServiceException ex) {
|
||||
allINodesDeleted = false;
|
||||
logger.log(Level.WARNING, String.format("Error deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
if (allINodesDeleted) {
|
||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a deleted item flag in the coordination service node data for the
|
||||
* case.
|
||||
*
|
||||
* @param flag The flag to set.
|
||||
*
|
||||
* @throws InterruptedException If the interrupted flag is set.
|
||||
*/
|
||||
private void setDeletedItemFlag(CaseNodeData.DeletedFlags flag) throws InterruptedException {
|
||||
try {
|
||||
caseNodeData.setDeletedFlag(flag);
|
||||
CaseNodeData.writeCaseNodeData(caseNodeData);
|
||||
} catch (CaseNodeDataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s", flag.name(), caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the interrupted flag of the current thread is set.
|
||||
*
|
||||
* @throws InterruptedException If the interrupted flag is set.
|
||||
*/
|
||||
private void checkForCancellation() throws InterruptedException {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException("Interrupt detected");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class that bundles a manifest file coordination service lock
|
||||
* with a manifest file path and a flag indicating whether or not the case
|
||||
* input (manifest file and data source) associated with the lock has been
|
||||
* deleted.
|
||||
*/
|
||||
private static class ManifestFileLock {
|
||||
|
||||
private final Path manifestFilePath;
|
||||
private final Lock lock;
|
||||
private boolean inputDeleted;
|
||||
|
||||
/**
|
||||
* Constructs an instance of a wrapper class that bundles a manifest
|
||||
* file coordination service lock with a manifest file path and a flag
|
||||
* indicating whether or not the case input (manifest file and data
|
||||
* source) associated with the lock has been deleted.
|
||||
*
|
||||
* @param manifestFilePath The manifest file path.
|
||||
* @param lock The coordination service lock.
|
||||
*/
|
||||
private ManifestFileLock(Path manifestFilePath, Lock lock) {
|
||||
this.manifestFilePath = manifestFilePath;
|
||||
this.lock = lock;
|
||||
this.inputDeleted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the manifest file associated with the lock.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Path getManifestFilePath() {
|
||||
return this.manifestFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag that indicates whether or not the case input (manifest
|
||||
* file and data source) associated with the lock has been deleted.
|
||||
*/
|
||||
private void setInputDeleted() {
|
||||
this.inputDeleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the flag that indicates whether or not the case
|
||||
* input (manifest file and data source) associated with the lock has
|
||||
* been deleted.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
private boolean isInputDeleted() {
|
||||
return this.inputDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the manifest file lock.
|
||||
*
|
||||
* @throws CoordinationServiceException If an error occurs while
|
||||
* releasing the lock.
|
||||
*/
|
||||
private void release() throws CoordinationServiceException {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
|
||||
/**
|
||||
* A utility class supplying helper methods for case deletion.
|
||||
*/
|
||||
final class DeleteCaseUtils {
|
||||
|
||||
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
|
||||
|
||||
/**
|
||||
* Examines a coordination service exception to try to determine if it is a
|
||||
* no node exception.
|
||||
*
|
||||
* @param ex A coordination service exception.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
static boolean isNoNodeException(CoordinationService.CoordinationServiceException ex) {
|
||||
boolean isNodeNodeEx = false;
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause != null) {
|
||||
String causeMessage = cause.getMessage();
|
||||
isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
|
||||
}
|
||||
return isNodeNodeEx;
|
||||
}
|
||||
|
||||
/**
|
||||
* A private constructor to prevent instantiation.
|
||||
*/
|
||||
private DeleteCaseUtils() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
|
||||
/**
|
||||
* An action that completely deletes one or more multi-user cases, including any
|
||||
* associated auto ingest job input directories and coordination service nodes.
|
||||
*
|
||||
* This cases to delete are discovered by querying the actions global context
|
||||
* lookup for CaseNodeData objects. See
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-1.html and
|
||||
* https://platform.netbeans.org/tutorials/nbm-selection-2.html for details.
|
||||
*/
|
||||
final class DeleteCasesAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an action that completely deletes one or more multi-user
|
||||
* cases, including any associated auto ingest job input directories and
|
||||
* coordination service nodes.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCasesAction.menuItemText=Delete Case and Jobs"
|
||||
})
|
||||
DeleteCasesAction() {
|
||||
super(Bundle.DeleteCasesAction_menuItemText());
|
||||
setEnabled(false); // RJCTODO: Enable when implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
// final Collection<CaseNodeData> selectedNodeData = new ArrayList<>(Utilities.actionsGlobalContext().lookupAll(CaseNodeData.class));
|
||||
// if (!selectedNodeData.isEmpty()) {
|
||||
// /*
|
||||
// * RJCTODO: Create a background task that does the deletion and
|
||||
// * displays results in a dialog with a scrolling text pane.
|
||||
// */
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteCasesAction clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An action class that kicks off a cancellable orphaned case nodes deletion
|
||||
* task that runs in a background thread and reports progress using an
|
||||
* application frame progress bar.
|
||||
*/
|
||||
final class DeleteOrphanCaseNodesAction extends BackgroundTaskAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an instance of an action class that kicks off a cancellable
|
||||
* orphaned case nodes deletion task that runs in a background thread and
|
||||
* reports progress using an application frame progress bar.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteOrphanCaseNodesAction.progressDisplayName=Cleanup Case Znodes"
|
||||
})
|
||||
DeleteOrphanCaseNodesAction() {
|
||||
super(Bundle.DeleteOrphanCaseNodesAction_progressDisplayName(), Bundle.DeleteOrphanCaseNodesAction_progressDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable getTask(ProgressIndicator progress) {
|
||||
return new DeleteOrphanCaseNodesTask(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteOrphanCaseNodesAction clone() throws CloneNotSupportedException {
|
||||
super.clone();
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeDataCollector;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* Task for deleting case coordination service nodes for which there is no
|
||||
* longer a corresponding case.
|
||||
*/
|
||||
final class DeleteOrphanCaseNodesTask implements Runnable {
|
||||
|
||||
private static final Logger logger = AutoIngestDashboardLogger.getLogger();
|
||||
private final ProgressIndicator progress;
|
||||
|
||||
/**
|
||||
* Constucts an instance of a task for deleting case coordination service
|
||||
* nodes for which there is no longer a corresponding case.
|
||||
*
|
||||
* @param progress
|
||||
*/
|
||||
DeleteOrphanCaseNodesTask(ProgressIndicator progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"DeleteOrphanCaseNodesTask.progress.startMessage=Starting orphaned case znode cleanup",
|
||||
"DeleteOrphanCaseNodesTask.progress.connectingToCoordSvc=Connecting to the coordination service",
|
||||
"DeleteOrphanCaseNodesTask.progress.gettingCaseNodesListing=Querying coordination service for case znodes"
|
||||
})
|
||||
public void run() {
|
||||
progress.start(Bundle.DeleteOrphanCaseNodesTask_progress_startMessage());
|
||||
try {
|
||||
progress.progress(Bundle.DeleteOrphanCaseNodesTask_progress_connectingToCoordSvc());
|
||||
logger.log(Level.INFO, Bundle.DeleteOrphanCaseNodesTask_progress_connectingToCoordSvc());
|
||||
CoordinationService coordinationService;
|
||||
try {
|
||||
coordinationService = CoordinationService.getInstance();
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, "Error connecting to the coordination service", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
progress.progress(Bundle.DeleteOrphanCaseNodesTask_progress_gettingCaseNodesListing());
|
||||
logger.log(Level.INFO, Bundle.DeleteOrphanCaseNodesTask_progress_gettingCaseNodesListing());
|
||||
List<CaseNodeData> nodeDataList;
|
||||
try {
|
||||
nodeDataList = CaseNodeDataCollector.getNodeData();
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, "Error collecting case node data", ex); //NON-NLS
|
||||
return;
|
||||
} catch (InterruptedException unused) {
|
||||
logger.log(Level.WARNING, "Task cancelled while collecting case node data"); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
for (CaseNodeData nodeData : nodeDataList) {
|
||||
final Path caseDirectoryPath = nodeData.getDirectory();
|
||||
final File caseDirectory = caseDirectoryPath.toFile();
|
||||
if (!caseDirectory.exists()) {
|
||||
String caseName = nodeData.getDisplayName();
|
||||
String nodePath = ""; // NON-NLS
|
||||
try {
|
||||
nodePath = CoordinationServiceUtils.getCaseNameNodePath(caseDirectoryPath);
|
||||
deleteNode(coordinationService, caseName, nodePath);
|
||||
|
||||
nodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirectoryPath);
|
||||
deleteNode(coordinationService, caseName, nodePath);
|
||||
|
||||
nodePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseDirectoryPath);
|
||||
deleteNode(coordinationService, caseName, nodePath);
|
||||
|
||||
nodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseDirectoryPath);
|
||||
deleteNode(coordinationService, caseName, nodePath);
|
||||
|
||||
} catch (InterruptedException unused) {
|
||||
logger.log(Level.WARNING, String.format("Task cancelled while deleting orphaned znode %s for %s", nodePath, caseName)); //NON-NLS
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
/*
|
||||
* This is an unexpected runtime exceptions firewall. It is here
|
||||
* because this task is designed to be able to be run in scenarios
|
||||
* where there is no call to get() on a Future<Void> associated with
|
||||
* the task, so this ensures that any such errors get logged.
|
||||
*/
|
||||
logger.log(Level.SEVERE, "Unexpected error during orphan case znode cleanup", ex); //NON-NLS
|
||||
throw ex;
|
||||
|
||||
} finally {
|
||||
progress.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete a case coordination service node.
|
||||
*
|
||||
* @param coordinationService The ccordination service.
|
||||
* @param caseName The case name.
|
||||
* @param nodePath The path of the node to delete.
|
||||
*
|
||||
* @throws InterruptedException If the thread executing this task is
|
||||
* interrupted during the delete operation.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - node path", "DeleteOrphanCaseNodesTask.progress.deletingOrphanedCaseNode=Deleting orphaned case znode {0}"
|
||||
})
|
||||
private void deleteNode(CoordinationService coordinationService, String caseName, String nodePath) throws InterruptedException {
|
||||
try {
|
||||
progress.progress(Bundle.DeleteOrphanCaseNodesTask_progress_deletingOrphanedCaseNode(nodePath));
|
||||
logger.log(Level.INFO, String.format("Deleting orphaned case node %s for %s", nodePath, caseName)); //NON-NLS
|
||||
coordinationService.deleteNode(CoordinationService.CategoryNode.CASES, nodePath);
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
if (!DeleteCaseUtils.isNoNodeException(ex)) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting orphaned case node %s for %s", nodePath, caseName), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* An action class that kicks off a cancellable orphaned manifest file nodes
|
||||
* deletion task that runs in a background thread and reports progress using an
|
||||
* application frame progress bar.
|
||||
*/
|
||||
public class DeleteOrphanManifestNodesAction extends BackgroundTaskAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an instance of an action class that kicks off a cancellable
|
||||
* orphaned manifest file nodes deletion task that runs in a background
|
||||
* thread and reports progress using an application frame progress bar.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteOrphanManifestNodesAction.progressDisplayName=Cleanup Manifest File Znodes"
|
||||
})
|
||||
DeleteOrphanManifestNodesAction() {
|
||||
super(Bundle.DeleteOrphanManifestNodesAction_progressDisplayName(), Bundle.DeleteOrphanManifestNodesAction_progressDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable getTask(ProgressIndicator progress) {
|
||||
return new DeleteOrphanManifestNodesTask(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteOrphanManifestNodesAction clone() throws CloneNotSupportedException {
|
||||
super.clone();
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
/**
|
||||
* A task class for cleaning up auto ingest job coordination service nodes for
|
||||
* which there is no longer a corresponding manifest file.
|
||||
*/
|
||||
final class DeleteOrphanManifestNodesTask implements Runnable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DeleteOrphanManifestNodesTask.class.getName());
|
||||
private final ProgressIndicator progress;
|
||||
|
||||
/**
|
||||
* Constucts an instance of a task for cleaning up case coordination service
|
||||
* nodes for which there is no longer a corresponding case.
|
||||
*
|
||||
* @param progress
|
||||
*/
|
||||
DeleteOrphanManifestNodesTask(ProgressIndicator progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"DeleteOrphanManifestNodesTask.progress.startMessage=Starting orphaned manifest file znode cleanup",
|
||||
"DeleteOrphanManifestNodesTask.progress.connectingToCoordSvc=Connecting to the coordination service",
|
||||
"DeleteOrphanManifestNodesTask.progress.gettingManifestNodes=Querying the coordination service for manifest file znodes",
|
||||
"# {0} - node path", "DeleteOrphanManifestNodesTask.progress.deletingOrphanedManifestNode=Deleting orphaned manifest file znode {0}"
|
||||
})
|
||||
public void run() {
|
||||
progress.start(Bundle.DeleteOrphanManifestNodesTask_progress_startMessage());
|
||||
try {
|
||||
progress.progress(Bundle.DeleteOrphanManifestNodesTask_progress_connectingToCoordSvc());
|
||||
logger.log(Level.INFO, Bundle.DeleteOrphanManifestNodesTask_progress_connectingToCoordSvc());
|
||||
CoordinationService coordinationService;
|
||||
try {
|
||||
coordinationService = CoordinationService.getInstance();
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, "Error connecting to the coordination service", ex); // NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
progress.progress(Bundle.DeleteOrphanManifestNodesTask_progress_gettingManifestNodes());
|
||||
logger.log(Level.INFO, Bundle.DeleteOrphanManifestNodesTask_progress_gettingManifestNodes());
|
||||
List<AutoIngestJobNodeData> nodeDataList;
|
||||
try {
|
||||
nodeDataList = AutoIngestJobNodeDataCollector.getNodeData();
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, "Error collecting auto ingest job node data", ex); // NON-NLS
|
||||
return;
|
||||
} catch (InterruptedException unused) {
|
||||
logger.log(Level.WARNING, "Task cancelled while collecting auto ingest job node data"); // NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
for (AutoIngestJobNodeData nodeData : nodeDataList) {
|
||||
final String caseName = nodeData.getCaseName();
|
||||
final Path manifestFilePath = nodeData.getManifestFilePath();
|
||||
final File manifestFile = manifestFilePath.toFile();
|
||||
if (!manifestFile.exists()) {
|
||||
try {
|
||||
progress.progress(Bundle.DeleteOrphanManifestNodesTask_progress_deletingOrphanedManifestNode(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Deleting orphaned manifest file znode %s for %s", manifestFilePath, caseName));
|
||||
coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath.toString());
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
if (!DeleteCaseUtils.isNoNodeException(ex)) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting %s znode for %s", manifestFilePath, caseName), ex); // NON-NLS
|
||||
}
|
||||
} catch (InterruptedException unused) {
|
||||
logger.log(Level.WARNING, String.format("Task cancelled while deleting %s znode for %s", manifestFilePath, caseName)); // NON-NLS
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
/*
|
||||
* This is an unexpected runtime exceptions firewall. It is here
|
||||
* because this task is designed to be able to be run in scenarios
|
||||
* where there is no call to get() on a Future<Void> associated with
|
||||
* the task, so this ensures that any such errors get logged.
|
||||
*/
|
||||
logger.log(Level.SEVERE, "Unexpected error deleting orphan manifest file znodes", ex); // NON-NLS
|
||||
throw ex;
|
||||
|
||||
} finally {
|
||||
progress.finish();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
|
||||
/**
|
||||
* An action that shows a popup that enumerates the deletion status of the
|
||||
* various parts of a multi-user case known to the coordination service.
|
||||
*/
|
||||
final class ShowCaseDeletionStatusAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
// private final CaseNodeData caseNodeData;
|
||||
|
||||
/**
|
||||
* Constructs an action that shows a popup that enumerates the deletion
|
||||
* status of the various parts of a multi-user case known to the
|
||||
* coordination service.
|
||||
*
|
||||
* @param caseNodeData The coordination service node data for the case.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"ShowCaseDeletionStatusAction.menuItemText=Show Deletion Status"
|
||||
})
|
||||
ShowCaseDeletionStatusAction(CaseNodeData caseNodeData) {
|
||||
super(Bundle.ShowCaseDeletionStatusAction_menuItemText());
|
||||
// this.caseNodeData = caseNodeData; // RJCTODO: Use
|
||||
setEnabled(false); // RJCTODO: Enable when implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// RJCTODO: Implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShowCaseDeletionStatusAction clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -514,7 +514,7 @@ public final class DrawableDB {
|
||||
private boolean initializeDBSchema() {
|
||||
dbWriteLock();
|
||||
try {
|
||||
boolean existingDB = true;
|
||||
boolean drawableDbTablesExist = true;
|
||||
|
||||
if (isClosed()) {
|
||||
logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS
|
||||
@@ -533,11 +533,11 @@ public final class DrawableDB {
|
||||
*/
|
||||
try (Statement stmt = con.createStatement()) {
|
||||
|
||||
// Check if the database is a new or existing database
|
||||
existingDB = doesTableExist("datasources");
|
||||
// Check if the database is new or an existing database
|
||||
drawableDbTablesExist = doesTableExist("drawable_files");
|
||||
if (false == doesTableExist(IG_DB_INFO_TABLE)) {
|
||||
try {
|
||||
VersionNumber ig_creation_schema_version = existingDB
|
||||
VersionNumber ig_creation_schema_version = drawableDbTablesExist
|
||||
? IG_STARTING_SCHEMA_VERSION
|
||||
: IG_SCHEMA_VERSION;
|
||||
|
||||
@@ -651,7 +651,8 @@ public final class DrawableDB {
|
||||
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
|
||||
|
||||
try {
|
||||
VersionNumber ig_creation_schema_version = existingDB
|
||||
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
|
||||
VersionNumber ig_creation_schema_version = caseDbTablesExist
|
||||
? IG_STARTING_SCHEMA_VERSION
|
||||
: IG_SCHEMA_VERSION;
|
||||
|
||||
@@ -935,13 +936,14 @@ public final class DrawableDB {
|
||||
*/
|
||||
private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction ) throws TskCoreException {
|
||||
|
||||
if (currVersion.getMajor() != 1 ||
|
||||
currVersion.getMinor() != 0) {
|
||||
// Upgrade if current version is 1.0
|
||||
// or 1.1 - a bug in versioning alllowed some databases to be versioned as 1.1 without the actual corresponding upgrade. This allows such databases to be fixed, if needed.
|
||||
if (!(currVersion.getMajor() == 1 &&
|
||||
(currVersion.getMinor() == 0 || currVersion.getMinor() == 1))) {
|
||||
return currVersion;
|
||||
}
|
||||
|
||||
// 1.0 -> 1.1 upgrade
|
||||
// Add a 'isAnalyzed' column to groups table in CaseDB
|
||||
// Add a 'is_analyzed' column to groups table in CaseDB
|
||||
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
|
||||
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction )) {
|
||||
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
|
||||
|
||||
+5
-4
@@ -700,7 +700,7 @@ public class GroupManager {
|
||||
try {
|
||||
DrawableFile file = getDrawableDB().getFileFromID(fileId);
|
||||
String pathVal = file.getDrawablePath();
|
||||
GroupKey<?> pathGroupKey = new GroupKey(DrawableAttribute.PATH,pathVal, file.getDataSource());
|
||||
GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH,pathVal, file.getDataSource());
|
||||
|
||||
updateCurrentPathGroup(pathGroupKey);
|
||||
} catch (TskCoreException | TskDataException ex) {
|
||||
@@ -712,7 +712,6 @@ public class GroupManager {
|
||||
for (GroupKey<?> gk : groupsForFile) {
|
||||
// see if a group has been created yet for the key
|
||||
DrawableGroup g = getGroupForKey(gk);
|
||||
|
||||
addFileToGroup(g, gk, fileId);
|
||||
}
|
||||
}
|
||||
@@ -816,8 +815,10 @@ public class GroupManager {
|
||||
controller.getCategoryManager().registerListener(group);
|
||||
groupMap.put(groupKey, group);
|
||||
}
|
||||
|
||||
if (analyzedGroups.contains(group) == false) {
|
||||
|
||||
// Add to analyzedGroups only if it's the same group type as the one in view
|
||||
if ((analyzedGroups.contains(group) == false) &&
|
||||
(getGroupBy() == group.getGroupKey().getAttribute())) {
|
||||
// Add to analyzedGroups only if this is the grouping being viewed.
|
||||
if (getGroupBy() == group.getGroupKey().getAttribute()) {
|
||||
analyzedGroups.add(group);
|
||||
|
||||
@@ -131,6 +131,7 @@ file.reference.uimaj-tools-2.6.0.jar=release/modules/ext/uimaj-tools-2.6.0.jar
|
||||
file.reference.vorbis-java-core-0.8.jar=release/modules/ext/vorbis-java-core-0.8.jar
|
||||
file.reference.vorbis-java-tika-0.8.jar=release/modules/ext/vorbis-java-tika-0.8.jar
|
||||
file.reference.woodstox-core-asl-4.4.1.jar=release/modules/ext/woodstox-core-asl-4.4.1.jar
|
||||
file.reference.wstx-asl-3.2.7.jar=release\\modules\\ext\\wstx-asl-3.2.7.jar
|
||||
file.reference.xmlbeans-2.6.0.jar=release/modules/ext/xmlbeans-2.6.0.jar
|
||||
file.reference.xmpcore-5.1.3.jar=release/modules/ext/xmpcore-5.1.3.jar
|
||||
file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user