Merge remote-tracking branch 'upstream/develop' into timeline-event-mgr

# Conflicts:
#	Core/build.xml
#	Core/src/org/sleuthkit/autopsy/casemodule/Case.java
#	Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
#	Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java
#	Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java
#	Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties-MERGED
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseKeywordSearchErrorDialog.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseKeywordSearchOpenAction.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseKeywordSearchPanel.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/MultiCaseSearcher.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/SearchQuery.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/SelectMultiUserCasesDialog.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/SelectMultiUserCasesPanel.java
#	RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java
#	RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java
This commit is contained in:
millmanorama 2019-04-16 15:28:40 +02:00
commit 01966ad498
224 changed files with 7170 additions and 7097 deletions

View File

@ -44,6 +44,23 @@
<copy todir="${basedir}/release/plaso" >
<fileset dir="${thirdparty.dir}/plaso"/>
</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}" />

View File

@ -61,7 +61,7 @@ file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-tran
file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar
file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar
file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar
file.reference.sleuthkit-postgresql-4.6.5.jar=release/modules/ext/sleuthkit-postgresql-4.6.5.jar
file.reference.sleuthkit-postgresql-4.6.6.jar=release/modules/ext/sleuthkit-postgresql-4.6.6.jar
file.reference.tagsoup-1.2.1.jar=release/modules/ext/tagsoup-1.2.1.jar
file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar
file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar

View File

@ -450,8 +450,8 @@
<binary-origin>release/modules/ext/jhighlight-1.0.2.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-postgresql-4.6.5.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-postgresql-4.6.5.jar</binary-origin>
<runtime-relative-path>ext/sleuthkit-postgresql-4.6.6.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-postgresql-4.6.6.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jempbox-1.8.13.jar</runtime-relative-path>

View File

@ -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...

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.casemodule;
import com.google.common.annotations.Beta;
import com.google.common.eventbus.Subscribe;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import java.awt.Frame;
@ -32,6 +33,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;
@ -125,7 +129,6 @@ import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TimelineManager;
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.
@ -145,6 +148,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();
@ -736,38 +740,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());
@ -777,32 +771,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 {
@ -810,43 +789,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.
*
@ -1024,105 +966,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.
*
@ -1138,7 +981,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) {
@ -1814,11 +1658,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);
}
}
@ -2199,10 +2042,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);
}
}
@ -2212,6 +2053,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
@ -2224,15 +2067,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);
}
}
}
@ -2369,7 +2209,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
@ -2378,6 +2217,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
@ -2728,6 +2568,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.

View File

@ -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");

View File

@ -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);
}
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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...

View File

@ -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;

View File

@ -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";
}
}

View File

@ -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.
*/

View File

@ -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;

View File

@ -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

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
@ -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>

View File

@ -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());
}

View File

@ -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>

View File

@ -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
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -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");
}
}

View File

@ -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(

View File

@ -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)"

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
<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, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 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 {
@ -230,9 +242,6 @@ public final class ExecUtil {
}
}
/**
* EVERYTHING FOLLOWING THIS LINE IS DEPRECATED AND SLATED FOR REMOVAL
*/
@Deprecated
private static final Logger logger = Logger.getLogger(ExecUtil.class.getName());

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -50,6 +50,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
@ -66,6 +67,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
validateSettings();
blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard();
this.context = context;
}
@Messages({
@ -76,8 +78,6 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
@Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
try {
if (dataSource instanceof Image) {
@ -91,10 +91,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));
}
@ -138,6 +151,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));
@ -193,7 +211,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;
}

View File

@ -80,6 +80,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;
@ -105,7 +106,9 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
try {
validateSettings();
this.context = context;
blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
fileTypeDetector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new IngestModule.IngestModuleException("Failed to create file type detector", ex);
@ -192,6 +195,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));
@ -391,7 +398,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/*
* Qualify the entropy.
*/
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
if (calculatedEntropy >= minimumEntropy) {
possiblyEncrypted = true;
}

View File

@ -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;
}
}
}
/*

View File

@ -1,3 +1,5 @@
# {0} - file that events are from
PlasoIngestModule_artifact_progress=Adding events to case: {0}
PlasoIngestModule_bad_imageFile=Cannot find image file name and path
PlasoIngestModule_completed=Plaso Processing Completed
PlasoIngestModule_create_artifacts_cancelled=Cancelled Plaso Artifact Creation

View File

@ -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();
}
}

View File

@ -3,3 +3,4 @@
# and open the template in the editor.
ProgressPanel.progressMessage.text=Message
TaskCanceller.progress.cancellingMessage=Cancelling...

View File

@ -128,6 +128,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
*
* @param cancellingMessage
*/
@Override
public synchronized void setCancelling(String cancellingMessage) {
cancelling = true;
SwingUtilities.invokeLater(() -> {

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -80,7 +80,7 @@ public final class FilterUtils {
}
@Override
protected String getSQLWhere(TimelineManager manager) {
public String getSQLWhere(TimelineManager manager) {
return " NOT " + super.getSQLWhere(manager);
}
}

View File

@ -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"/>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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",

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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">

View File

@ -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;

View File

@ -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();
}
/**

View File

@ -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() {
}
}

View File

@ -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());

View File

@ -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.
}
/**

View File

@ -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;

View File

@ -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() {
}
}

View File

@ -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:

View File

@ -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();
}

View File

@ -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.
*

View File

@ -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();
}
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteOrphanManifestNodesButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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() {
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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
}
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More