Interim check in of adminstrative case deletion

This commit is contained in:
Richard Cordovano 2019-03-11 11:52:00 -04:00
parent ad227a3db5
commit 486fbfe152
17 changed files with 808 additions and 646 deletions

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.
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
@ -34,7 +34,7 @@ Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case datab
Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}.
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 log for details.
# {0} - exception message
Case.exceptionMessage.execExceptionWrapperMessage={0}
# {0} - exception message
@ -58,36 +58,12 @@ 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.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log lock coordination service node...
Case.progressMessage.deletingCaseDatabase=Deleting case database...
Case.progressMessage.deletingCaseDirectory=Deleting case directory...
Case.progressMessage.deletingDirectoryCoordinationServiceNode=Deleting case directory lock coordination service node...
Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resourceslock coordination service node...
Case.progressMessage.deletingCaseDirLockNode=Deleting case directory lock coordination service node...
Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node...
Case.progressMessage.deletingTextIndex=Deleting text index...
# {0} - exception message
Case.progressMessage.errorConnectingToCoordSvc=An error occurred connecting to the coordination service (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorDeletingCaseDb=An error occurred deleting the case database (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorDeletingCaseDir=An error occurred deleting the case directory (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorDeletingCaseDirLockNode=An error occurred deleting the case directory lock node (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorDeletingJobLogLockNode=An error occurred deleting the case auto ingest log lock node (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorDeletingResourcesLockNode=An error occurred deleting the case resources lock node (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorDeletingTextIndex=An error occurred deleting the text index for the case (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorLockingCase=An error occurred exclusively locking the case for deletion (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}.
# {0} - exception message
Case.progressMessage.errorSavingDeletedItemsFlags=An error occurred saving the deleted items flags in the coordination service database (see log for details): {0}.
Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...
Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data.
Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...
Case.progressMessage.openingCaseDatabase=Opening case database...
Case.progressMessage.openingCaseLevelServices=Opening case-level services...

View File

@ -702,8 +702,45 @@ public class Case {
}
CaseMetadata metadata = currentCase.getMetadata();
closeCurrentCase();
ProgressIndicator progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
deleteCase(metadata, true, progressIndicator);
deleteCase(metadata);
}
}
/**
* Deletes a case. The case to be deleted must not be the "current case."
*
* @param metadata The case metadata.
*
* @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.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first."
})
public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
synchronized (caseActionSerializationLock) {
if (null != currentCase) {
throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
}
}
ProgressIndicator progressIndicator;
if (RuntimeProperties.runningWithGUI()) {
progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
} else {
progressIndicator = new LoggingProgressIndicator();
}
progressIndicator.start(Bundle.Case_progressMessage_preparing());
try {
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
deleteSingleUserCase(metadata, progressIndicator);
} else {
deleteMultiUserCase(metadata, progressIndicator);
}
} finally {
progressIndicator.finish();
}
}
@ -884,40 +921,6 @@ public class Case {
return imgPaths;
}
/**
* Deletes a case. The case to be deletd must not be the "current case."
*
* @param metadata The case metadata.
* @param deleteCaseLockNodes Whether or not to delete the coordination
* service lock nodes for the case.
* @param progressIndicator A progress indicator.
*
* @throws CaseActionException If there were one or more errors deleting the
* case.
*/
@Beta
@Messages({
"Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first."
})
public static void deleteCase(CaseMetadata metadata, boolean deleteCaseLockNodes, ProgressIndicator progressIndicator) throws CaseActionException {
synchronized (caseActionSerializationLock) {
if (null != currentCase) {
throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
}
}
progressIndicator.start(Bundle.Case_progressMessage_preparing());
try {
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
deleteSingleUserCase(metadata, progressIndicator);
} else {
deleteMultiUserCase(metadata, progressIndicator);
}
} finally {
progressIndicator.finish();
}
}
/**
* Deletes a single-user case.
*
@ -925,12 +928,12 @@ public class Case {
* @param progressIndicator A progress indicator.
*
* @throws CaseActionException If there were one or more errors deleting the
* case.
* 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.",
"# {0} - exception message", "Case.progressMessage.errorDeletingTextIndex=An error occurred deleting the text index for the case (see log for details): {0}.",
"# {0} - exception message", "Case.progressMessage.errorDeletingCaseDir=An error occurred deleting the case directory (see log for details): {0}."
"Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details."
})
private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
boolean errorsOccurred = false;
@ -938,16 +941,14 @@ public class Case {
deleteTextIndex(metadata, progressIndicator);
} catch (KeywordSearchServiceException ex) {
errorsOccurred = true;
logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingTextIndex(ex.getMessage()));
logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
}
try {
deleteCaseDirectory(metadata, progressIndicator);
} catch (CaseActionException ex) {
errorsOccurred = true;
logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDir(ex.getMessage()));
logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
}
deleteFromRecentCases(metadata, progressIndicator);
@ -961,61 +962,37 @@ public class Case {
* Deletes a multi-user case.
*
* @param metadata The case metadata.
* @param deleteCaseLockNodes Whether or not to delete the coordination
* service case directory and case name lock
* nodes for the case.
* @param progressIndicator A progress indicator.
*
* @throws CaseActionException If there were one or more errors deleting the
* case.
* case. The exception will have a user-friendly
* message and may be a wrapper for a
* lower-level exception.
*/
@Messages({
"Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
"# {0} - exception message", "Case.progressMessage.errorConnectingToCoordSvc=An error occurred connecting to the coordination service (see log for details): {0}.",
"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.",
"# {0} - exception message", "Case.progressMessage.errorLockingCaseName=An error occurred exclusively locking the case name for deletion (see log for details): {0}.",
"# {0} - exception message", "Case.progressMessage.errorLockingCase=An error occurred exclusively locking the case for deletion (see log for details): {0}.",
"Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
"# {0} - exception message", "Case.progressMessage.errorGettingCoordSvcNodeData=Failed to get the coordination service case node data (see log for details): {0}.",
"Case.progressMessage.missingCoordSvcNodeData=Missing coordination service node data.",
"# {0} - exception message", "Case.progressMessage.errorDeletingCaseDb=An error occurred deleting the case database (see log for details): {0}.",
"# {0} - exception message", "Case.progressMessage.errorSavingDeletedItemsFlags=An error occurred saving the deleted items flags in the coordination service database (see log for details): {0}."
"Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
"Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case..."
})
private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
CaseNodeData caseNodeData;
boolean errorsOccurred = false;
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);
progressIndicator.progress(Bundle.Case_progressMessage_errorConnectingToCoordSvc(ex.getMessage()));
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
}
/*
* Acquire an exclusive case name lock. This will prevent auto ingest
* nodes from attempting to search for the case directory before it is
* deleted.
*/
progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
final String caseNameLockName = TimeStampUtils.removeTimeStamp(metadata.getCaseName());
try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) {
if (nameLock == null) {
logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because the case name was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
}
/*
* Acquire an exclusive case directory lock. This ensures that no
* other node (host) currently has the case open and prevents
* another node (host) from trying to open the case as it is being
* deleted.
*/
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 the case was in use by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
progressIndicator.progress(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
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()));
throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
}
@ -1025,108 +1002,143 @@ public class Case {
if (nodeBytes != null && nodeBytes.length > 0) {
caseNodeData = new CaseNodeData(nodeBytes);
} else {
logger.log(Level.SEVERE, String.format("Missing coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
progressIndicator.progress(Bundle.Case_progressMessage_missingCoordSvcNodeData());
logger.log(Level.WARNING, String.format("Missing coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
}
} catch (CoordinationServiceException | InterruptedException | IOException 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);
progressIndicator.progress(Bundle.Case_progressMessage_errorGettingCoordSvcNodeData(ex.getMessage()));
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
}
try {
deleteCaseDatabase(metadata, progressIndicator, caseNodeData);
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
errorsOccurred = true;
logger.log(Level.SEVERE, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDb(ex.getMessage()));
}
try {
deleteTextIndex(metadata, progressIndicator, caseNodeData);
} catch (KeywordSearchServiceException ex) {
errorsOccurred = true;
logger.log(Level.SEVERE, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingTextIndex(ex.getMessage()));
}
try {
deleteCaseDirectory(metadata, progressIndicator, caseNodeData);
} catch (CaseActionException ex) {
errorsOccurred = true;
logger.log(Level.SEVERE, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDir(ex.getMessage()));
}
deleteFromRecentCases(metadata, progressIndicator);
errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator);
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorLockingCase(ex.getMessage()));
logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
}
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to exclusively lock the case name for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorLockingCaseName(ex.getMessage()));
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
}
deleteCaseResourcesCoordinationServiceNode(metadata, progressIndicator, coordinationService);
deleteCaseAutoIngestLogCoordinationServiceNode(metadata, progressIndicator, coordinationService);
// RJCTODO: Delete case name lock node
if (!errorsOccurred) {
deleteCaseDirectoryCoordinationServiceNode(metadata, progressIndicator, coordinationService);
} else {
/*
* Save the deleted item flags for the partially deleted case so
* that system adminstrators can try to fix whatever wnet wrong and
* then retry deleting the case.
*/
try {
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), caseNodeData.toArray());
} catch (IOException | CoordinationServiceException | InterruptedException ex) {
logger.log(Level.SEVERE, String.format("Failed to write the node dat with the deleted items flags for the partially deleted case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorSavingDeletedItemsFlags(ex.getMessage()));
deleteCaseDirectoryLockNode(caseNodeData, progressIndicator);
} 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);
errorsOccurred = true;
}
}
if (errorsOccurred) {
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
}
}
/**
* Attempts to delete the case database for a multi-user case.
* IMPORTANT: This is a "beta" method and is subject to change or removal
* without notice!
*
* Attempts to delete the case database, the text index, the case directory,
* and the case resources coordination service lock code for a case and
* removes the case from the recent cases menu of the mian application
* window.
*
* @param caseNodeData The coordination service node data for the case.
* @param metadata The case metadata.
* @param progressIndicator A progress indicator.
* @param caseNodeData The coordination service node data for the case.
*
* @throws UserPreferencesException If there is an error getting the
* @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 opersation is detected
* during a wait.
*/
@Beta
public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws InterruptedException {
boolean errorsOccurred = false;
try {
deleteCaseDatabase(caseNodeData, 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);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return errorsOccurred;
}
try {
deleteTextIndex(caseNodeData, metadata, progressIndicator);
} 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);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return errorsOccurred;
}
try {
deleteCaseDirectory(caseNodeData, metadata, progressIndicator);
} 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);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return errorsOccurred;
}
deleteFromRecentCases(metadata, progressIndicator);
if (Thread.currentThread().isInterrupted()) {
Thread.currentThread().interrupt();
return errorsOccurred;
}
try {
deleteCaseResourcesLockNode(caseNodeData, progressIndicator);
} catch (CoordinationServiceException ex) {
errorsOccurred = true;
logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return errorsOccurred;
}
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.
*
* @throws UserPreferencesException if there is an error getting the
* database server connection info.
* @throws ClassNotFoundException If there is an error gettting the
* @throws ClassNotFoundException if there is an error gettting the
* required JDBC driver.
* @throws SQLException If there is an error executing the SQL
* @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 deleteCaseDatabase(CaseMetadata metadata, ProgressIndicator progressIndicator,
CaseNodeData caseNodeData) throws UserPreferencesException, ClassNotFoundException, SQLException {
if (caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
return;
}
"Case.progressMessage.deletingCaseDatabase=Deleting case database..."
})
private static void deleteCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
CaseDbConnectionInfo db;
db = UserPreferences.getDatabaseConnectionInfo();
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("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
Statement statement = connection.createStatement();) {
try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
statement.execute(deleteCommand);
}
caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.CASE_DB);
setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DB);
}
}
/**
@ -1151,19 +1163,21 @@ public class Case {
/**
* 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 mnetadata.
* @param progressIndicator A progress indicator.
* @param caseNodeData The coordination service node data for the case
* if it is a multi-user case; for a single-user
* case pass null.
*
* @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 deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws KeywordSearchServiceException {
private static void deleteTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException, InterruptedException {
if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.TEXT_INDEX)) {
deleteTextIndex(metadata, progressIndicator);
caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.TEXT_INDEX);
setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.TEXT_INDEX);
}
}
@ -1180,27 +1194,32 @@ public class Case {
"Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
})
private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
// RJCTODO: Update FileUtil.deleteDir to use robocopy on Windows
// when the path is >= 255 chars. Actually, deprecate this method and
// replace it with one that throws instead of returning a boolean value.
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
// RJCTODO: Use robocopy on Windows, possibly just for longer paths
if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory()));
}
}
/**
* Attempts to delete the case directory for a case.
* 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 mnetadata.
* @param progressIndicator A progress indicator.
* @param caseNodeData The coordination service node data for the case.
*
* @throws CaseActionException If there is an error deleting the case
* @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 deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator, CaseNodeData caseNodeData) throws CaseActionException {
private static void deleteCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
deleteCaseDirectory(metadata, progressIndicator);
caseNodeData.setDeletedFlag(CaseNodeData.DeletedFlags.CASE_DIR);
setDeletedItemFlag(caseNodeData, CaseNodeData.DeletedFlags.CASE_DIR);
}
}
@ -1223,71 +1242,82 @@ public class Case {
}
}
// RJCTODO: Copy-paste instead
/**
* Deletes the case resources coordination nodes for a multi-user case.
* IMPORTANT: This is a "beta" method and is subject to change or removal
* without notice!
*
* @param metadata The case metadata.
* @param progressIndicator A progress indicator.
*/
@Messages({
"Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resourceslock coordination service node...",
"# {0} - exception message", "Case.progressMessage.errorDeletingResourcesLockNode=An error occurred deleting the case resources lock node (see log for details): {0}."
})
static void deleteCaseResourcesCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) {
progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
String resourcesLockNodePath = metadata.getCaseDirectory() + RESOURCES_LOCK_SUFFIX;
try {
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingResourcesLockNode(ex.getMessage()));
}
}
/**
* Deletes the case auto ingest log lock coordination service node for a
* multi-user case, if it exists.
*
* @param metadata The case metadata.
* @param progressIndicator A progress indicator.
*/
@Messages({
"Case.progressMessage.deletingAutoIngestLogCoordSvcNode=Deleting case auto ingest log lock coordination service node...",
"# {0} - exception message", "Case.progressMessage.errorDeletingJobLogLockNode=An error occurred deleting the case auto ingest log lock node (see log for details): {0}."
})
static void deleteCaseAutoIngestLogCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) {
progressIndicator.progress(Bundle.Case_progressMessage_deletingAutoIngestLogCoordSvcNode());
Path logFilePath = Paths.get(metadata.getCaseDirectory(), AUTO_INGEST_LOG_FILE_NAME);
try {
/*
* Does nothing if the node does not exist.
*/
coordinationService.deleteNode(CategoryNode.CASES, logFilePath.toString());
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error deleting the case auto ingest log lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingJobLogLockNode(ex.getMessage()));
}
}
/**
* Deletes the case (directory lock) coordination service node for a
* Deletes the case resources lock coordination service node for a
* multi-user case.
*
* @param metadata The metadata for the case to delete.
* @param caseNodeData The coordination service node data for the case.
* @param progressIndicator The progress indicator for the deletion
* operation.
*
* @throws CoordinationServiceException If there is an error deleting the
* coordination service node.
* @throws InterruptedException If a thread interrupt occurs while
* blocked waiting for the operation to
* complete.
*/
@Messages({
"Case.progressMessage.deletingDirectoryCoordinationServiceNode=Deleting case directory lock coordination service node...",
"# {0} - exception message", "Case.progressMessage.errorDeletingCaseDirLockNode=An error occurred deleting the case directory lock node (see log for details): {0}."
"Case.progressMessage.deletingResourcesLockNode=Deleting case resources lock node..."
})
static void deleteCaseDirectoryCoordinationServiceNode(CaseMetadata metadata, ProgressIndicator progressIndicator, CoordinationService coordinationService) {
String caseDirectoryLockNodePath = metadata.getCaseDirectory();
try {
@Beta
private static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException {
progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode());
String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;//RJCTODO: Use utility
CoordinationService coordinationService = CoordinationService.getInstance();
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
}
// RJCTODO: Copy-paste instead
/**
* IMPORTANT: This is a "beta" method and is subject to change or removal
* without notice!
*
* Deletes the case directory lock coordination service node for a
* multi-user case.
*
* @param caseNodeData The coordination service node data for the case.
* @param progressIndicator The progress indicator for the deletion
* operation.
*
* @throws CoordinationServiceException If there is an error deleting the
* coordination service node.
* @throws InterruptedException If a thread interrupt occurs while
* blocked waiting for the operation to
* complete.
*/
@Beta
@Messages({
"Case.progressMessage.deletingCaseDirLockNode=Deleting case directory lock coordination service node..."
})
public static void deleteCaseDirectoryLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException {
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirLockNode());
String caseDirectoryLockNodePath = caseNodeData.getDirectory().toString();
CoordinationService coordinationService = CoordinationService.getInstance();
coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath);
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error deleting the case directory lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
progressIndicator.progress(Bundle.Case_progressMessage_errorDeletingCaseDirLockNode(ex.getMessage()));
}
/**
* 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);
CoordinationService coordinationService = CoordinationService.getInstance();
coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray());
} catch (IOException | CoordinationServiceException 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);
}
}
@ -3310,31 +3340,4 @@ public class Case {
deleteReports(reports);
}
/**
* Deletes a case.
*
* @param metadata The metadata for the case to delete.
*
* @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.
* @deprecated Use deleteCase(CaseMetadata, ProgressIndicator) instead.
* instead.
*/
@Deprecated
public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
/*
* 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());
} else {
progressIndicator = new LoggingProgressIndicator();
}
deleteCase(metadata, true, progressIndicator);
}
}

View File

@ -289,7 +289,7 @@ public final class CaseNodeData {
CASE_DB(2),
CASE_DIR(4),
DATA_SOURCES(8),
JOB_MANIFEST_NODES(16);
MANIFEST_FILE_LOCK_NODES(16);
private final short value;

View File

@ -29,76 +29,60 @@ 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.coordinationservice.CoordinationService.CoordinationServiceException;
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 {
final public class MultiUserCaseNodeDataCollector { // RJCTODO: Shorten name after multi-case keyword search code is in.
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.
* @return The node data for the multi-user cases known to the coordination
* service.
*
* @throws CoordinationServiceException If there is an error
* @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 CoordinationService.CoordinationServiceException {
public static List<CaseNodeData> getNodeData() throws CoordinationServiceException, InterruptedException {
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)) {
if (CaseCoordinationServiceUtils.isCaseLockName(nodeName)
|| CaseCoordinationServiceUtils.isCaseResourcesLockName(nodeName)
|| CaseCoordinationServiceUtils.isCaseAutoIngestLogLockName(nodeName)) {
continue;
}
/*
* Get the data from the case directory lock node. This data may not
* exist for "legacy" nodes. If it is missing, create it.
* exist or may exist only in an older version. If it is missing or
* incomplete, create or update it.
*/
try {
CaseNodeData nodeData;
byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName);
final 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);
if (nodeData.getVersion() < CaseNodeData.getCurrentVersion()) {
nodeData = updateNodeData(nodeName, nodeData);
}
} else {
nodeData = createNodeDataFromCaseMetadata(nodeName, false);
nodeData = updateNodeData(nodeName, null);
}
if (nodeData != null) {
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);
@ -109,15 +93,15 @@ final public class MultiUserCaseNodeDataCollector {
}
/**
* Creates and saves case directory lock coordination service node data from
* the metadata file for the case associated with the node.
* Updates the case directory lock coordination service node data for a
* case.
*
* @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.
* @param oldNodeData .
*
* @return A CaseNodedata object.
* @return A CaseNodedata object or null if the coordination service node is
* an "orphan" with no corresponding case directry.
*
* @throws IOException If there is an error writing the
* node data to a byte array.
@ -130,28 +114,67 @@ final public class MultiUserCaseNodeDataCollector {
* @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;
private static CaseNodeData updateNodeData(String nodeName, CaseNodeData oldNodeData) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException {
Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS);
File caseDirectory = caseDirectoryPath.toFile();
if (caseDirectory.exists()) {
if (!caseDirectory.exists()) {
logger.log(Level.WARNING, String.format("Found orphan coordination service node %s, attempting clean up", caseDirectoryPath));
deleteLockNodes(CoordinationService.getInstance(), caseDirectoryPath);
return null;
}
CaseNodeData nodeData = null;
if (oldNodeData == null || oldNodeData.getVersion() == 0) {
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);
if (oldNodeData != null) {
/*
* Version 0 case node data was only written if errors
* occurred during an auto ingest job.
*/
nodeData.setErrorsOccurred(true);
}
break;
}
}
}
if (nodeData != null) {
CoordinationService coordinationService = CoordinationService.getInstance();
coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray());
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray());
}
return nodeData;
} else {
throw new IOException(String.format("Could not find case metadata file for %s", nodeName));
}
/**
* Attempts to delete the coordination service lock nodes for a case,
* logging any failures.
*
* @param coordinationService The coordination service.
* @param caseDirectoryPath The case directory path.
*/
private static void deleteLockNodes(CoordinationService coordinationService, Path caseDirectoryPath) {
deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirectoryPath));
deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseDirectoryPath));
deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath));
deleteCoordinationServiceNode(coordinationService, CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath));
}
/**
* Attempts to delete a coordination service node, logging failure.
*
* @param coordinationService The coordination service.
* @param nodeName A node name.
*/
private static void deleteCoordinationServiceNode(CoordinationService coordinationService, String nodeName) {
try {
coordinationService.deleteNode(CoordinationService.CategoryNode.CASES, nodeName);
} catch (CoordinationService.CoordinationServiceException | InterruptedException ex) {
logger.log(Level.WARNING, String.format("Error deleting coordination service node %s", nodeName), ex);
}
}

View File

@ -1,5 +1,10 @@
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.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node Deleted
MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted
MultiUserCasesBrowserPanel.waitNode.message=Please Wait...

View File

@ -141,14 +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.textIndexDeleteStatus=Text Index Deleted",
"MultiUserCaseBrowserCustomizer.column.caseDbDeleteStatus=Case Database Deleted",
"MultiUserCaseBrowserCustomizer.column.caseDirDeleteStatus=Case Directory Deleted",
"MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Deleted",
"MultiUserCaseBrowserCustomizer.column.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node 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());
// RJCTODO: Add properties for deleted items flags
LAST_ACCESS_DATE(Bundle.MultiUserCaseBrowserCustomizer_column_lastAccessTime()),
TEXT_INDEX_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_textIndexDeleteStatus()),
CASE_DB_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDbDeleteStatus()),
CASE_DIR_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_caseDirDeleteStatus()),
DATA_SOURCES_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_dataSourcesDeleteStatus()),
MANIFEST_FILE_LOCK_NODES_DELETION_STATUS(Bundle.MultiUserCaseBrowserCustomizer_column_manifestCoordSvcNodesDeleteStatus());
private final String displayName;

View File

@ -65,7 +65,7 @@ final class MultiUserCasesRootNode extends AbstractNode {
try {
List<CaseNodeData> caseNodeData = MultiUserCaseNodeDataCollector.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

@ -365,15 +365,22 @@ public final class CoordinationService {
*
* @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) {
if (ex instanceof InterruptedException) {
throw (InterruptedException) ex;
} else {
throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex);
}
}
}
/**
* Gets a list of the child nodes of a category in the namespace.
@ -384,15 +391,22 @@ public final class CoordinationService {
*
* @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) {
if (ex instanceof InterruptedException) {
throw (InterruptedException) ex;
} else {
throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex);
}
}
}
/**
* Creates a node path within a given category.

View File

@ -2292,9 +2292,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
sysLogger.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()});
currentJob.setProcessingStage(AutoIngestJob.Stage.OPENING_CASE, Date.from(Instant.now()));
/*
* Acquire and hold a case name lock so that only one node at as
* time can scan the output directory at a time. This prevents
* making duplicate cases for the same auto ingest case.
* Acquire and hold a case name lock so that only one node at a time
* can search the output directory for an existing case. This
* prevents making duplicate cases for the same auto ingest case.
*/
try (Lock caseLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseName, 30, TimeUnit.MINUTES)) {
if (null != caseLock) {

View File

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

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

View File

@ -180,26 +180,31 @@ DeleteCaseInputAndOutputAction.taskName=input-and-output
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.acquiringCaseDirLock=Acquiring an exclusive case directory lock...
DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock...
DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks
DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service
DeleteCaseTask.progress.deletingCaseOutput=Deleting case output
DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock node
DeleteCaseTask.progress.acquiringManifestFileLocks=Acquiring exclusive manifest file locks...
DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...
DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...
DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory...
DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...
# {0} - input directory name
DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}
DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}...
DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes
DeleteCaseTask.progress.deletingInputDirs=Deleting input directory
DeleteCaseTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node
DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node
DeleteCaseTask.progress.deletingInputDirs=Deleting input directory...
DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job log lock node...
DeleteCaseTask.progress.deletingManifestFileLockNodes=Deleting manifest file lock nodes...
DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node...
DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node
DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs
DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file
# {0} - input directory name
DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}
DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs...
DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...
DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on manifest {0}
# {0} - manifest file name
DeleteCaseTask.progress.lockingManifestFile=Acquiring exclusive lock on manifest {0}...
# {0} - manifest file path
DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}
DeleteCaseTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks
DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}...
DeleteCaseTask.progress.releasingManifestLocks=Releasing exclusive manifest file locks...
DeleteCaseTask.progress.startMessage=Preparing for deletion...
GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz)
HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.

View File

@ -38,19 +38,24 @@ final class DeleteCaseInputAndOutputTask extends DeleteCaseTask {
}
@Override
void deleteWhileHoldingAllLocks() {
void deleteWhileHoldingAllLocks() throws InterruptedException {
deleteInputDirectories();
deleteCaseOutput();
}
@Override
void deleteAfterCaseLocksReleased() {
deleteCaseLockNodes();
void deleteAfterManifestLocksReleased() throws InterruptedException {
deleteManifestFileLockNodes();
}
@Override
void deleteAfterAllLocksReleased() {
deleteInputDirectoryLockNodes();
void deleteAfterCaseDirectoryLockReleased() throws InterruptedException {
this.deleteCaseDirectoryLockNode();
}
@Override
void deleteAfterCaseNameLockReleased() throws InterruptedException {
this.deleteCaseNameLockNode();
}
}

View File

@ -24,18 +24,18 @@ import org.sleuthkit.autopsy.progress.ProgressIndicator;
/**
* A task to delete the auto ingest job input directories for a case produced
* via auto ingest, while leaving the auto ingest job coordination service nodes
* and the rest of the case intact. The use case is freeing space while
* retaining the option to restore the input directories, effectively restoring
* the case.
* (manifest file lock nodes) and the rest of the case intact. The use case is
* freeing space while retaining the option to restore the input directories,
* effectively restoring the case.
*/
final class DeleteCaseInputTask extends DeleteCaseTask {
/**
* Constructs a task to delete the auto ingest job input directories for a
* Constructs task to delete the auto ingest job input directories for a
* case produced via auto ingest, while leaving the auto ingest job
* coordination service nodes and the rest of the case intact. The use case
* is freeing space while retaining the option to restore the input
* directories, effectively restoring the case.
* coordination service nodes (manifest file lock nodes) and the rest of the
* case intact. The use case is freeing space while retaining the option to
* restore the input directories, effectively restoring the case.
*
* @param caseNodeData The case directory lock coordination service node
* data for the case.
@ -46,16 +46,20 @@ final class DeleteCaseInputTask extends DeleteCaseTask {
}
@Override
void deleteWhileHoldingAllLocks() {
void deleteWhileHoldingAllLocks() throws InterruptedException {
}
@Override
void deleteAfterCaseDirectoryLockReleased() throws InterruptedException {
}
@Override
void deleteAfterCaseNameLockReleased() throws InterruptedException {
deleteInputDirectories();
}
@Override
void deleteAfterCaseLocksReleased() {
}
@Override
void deleteAfterAllLocksReleased() {
void deleteAfterManifestLocksReleased() throws InterruptedException {
}
}

View File

@ -18,12 +18,7 @@
*/
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.progress.ProgressIndicator;

View File

@ -45,17 +45,22 @@ final class DeleteCaseOutputTask extends DeleteCaseTask {
}
@Override
void deleteWhileHoldingAllLocks() {
void deleteWhileHoldingAllLocks() throws InterruptedException {
deleteCaseOutput();
}
@Override
void deleteAfterCaseLocksReleased() {
void deleteAfterCaseDirectoryLockReleased() throws InterruptedException {
}
@Override
void deleteAfterAllLocksReleased() {
deleteInputDirectoryLockNodes();
void deleteAfterManifestLocksReleased() throws InterruptedException {
deleteManifestFileLockNodes();
}
@Override
void deleteAfterCaseNameLockReleased() throws InterruptedException {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}

View File

@ -19,6 +19,7 @@
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;
@ -29,138 +30,74 @@ import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUtils;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.progress.ProgressIndicator;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException;
/**
* A base class for tasks that delete part or all of a case produced via auto
* ingest.
* A base class for tasks that delete part or all of a given case.
*/
abstract class DeleteCaseTask implements Runnable {
private static final String RESOURCES_LOCK_SUFFIX = "_resources"; //NON-NLS
private static final Logger logger = AutoIngestDashboardLogger.getLogger();
private final CaseNodeData caseNodeData;
private final String caseDisplayName;
private final String caseUniqueName;
private final Path caseDirectoryPath;
private final ProgressIndicator progress;
private final List<AutoIngestJobNodeData> nodeDataForAutoIngestJobs;
private final Map<Path, CoordinationService.Lock> manifestFileLocks;
private final Map<String, CoordinationService.Lock> manifestFileLocks;
private CoordinationService coordinationService;
/**
* Constructs the base class part of a task that deletes part or all of a
* case produced via auto ingest.
* given case.
*
* @param caseNodeData The case directory lock coordination service node
* data for the case to be deleted.
* data for the case.
* @param progress A progress indicator.
*/
DeleteCaseTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
this.caseNodeData = caseNodeData;
this.progress = progress;
/*
* Design Decision Note: It was decided to add the following state to
* instances of this class make it easier to access given that the class
* design favors instance methods over static methods.
*/
this.caseDisplayName = caseNodeData.getDisplayName();
this.caseUniqueName = caseNodeData.getName();
this.caseDirectoryPath = caseNodeData.getDirectory();
this.nodeDataForAutoIngestJobs = new ArrayList<>();
this.manifestFileLocks = new HashMap<>();
}
@Override
@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.gettingJobNodeData=Getting node data for auto ingest jobs",
"DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks"
"DeleteCaseTask.progress.startMessage=Preparing for deletion..."
})
public void run() {
try {
progress.start(Bundle.DeleteCaseTask_progress_connectingToCoordSvc());
try {
coordinationService = CoordinationService.getInstance();
} catch (CoordinationService.CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to connect to the coordination service to delete %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
return;
}
/*
* Acquire an exclusive case name lock. This is the lock that auto
* ingest nodes acquire when creating or opening a case specified in
* an auto ingest job manifest file. Acquiring this lock prevents
* auto ingest nodes from searching for and finding the case
* directory of the case to be deleted.
*/
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock());
logger.log(Level.INFO, String.format("Exclusively locking the case name for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName());
try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockName)) {
if (nameLock == null) {
logger.log(Level.WARNING, String.format("Failed to exclusively lock the case name for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
return;
}
/*
* Acquire an exclusive case directory lock. This is the lock
* that is aquired by any node (auto ingest or examiner)
* attempting to create or open a case and is held by such a
* node for as long as the case is open. Acquiring this lock
* ensures that no other node currently has the case to be
* deleted open and prevents another node from trying to open
* the case as it is being deleted.
*/
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock());
logger.log(Level.INFO, String.format("Exclusively locking the case directory for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
try (CoordinationService.Lock caseLock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseNodeData.getDirectory().toString())) {
if (caseLock == null) {
logger.log(Level.WARNING, String.format("Failed to exclusively lock the case directory for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
return;
}
progress.progress(Bundle.DeleteCaseTask_progress_gettingJobNodeData());
logger.log(Level.INFO, String.format("Fetching auto ingest job node data for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
try {
getAutoIngestJobNodeData();
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to fetch auto ingest job node data for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
return;
}
if (!nodeDataForAutoIngestJobs.isEmpty()) {
progress.progress(Bundle.DeleteCaseTask_progress_acquiringInputDirLocks());
logger.log(Level.INFO, String.format("Exclusively locking the case directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
getInputDirectoryLocks();
if (manifestFileLocks.isEmpty()) {
logger.log(Level.WARNING, String.format("Failed to exclusively lock the input directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
return;
}
} else {
logger.log(Level.INFO, String.format("No auto ingest job node data found for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
}
deleteWhileHoldingAllLocks();
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error acquiring exclusive case directory lock for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
}
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error acquiring exclusive case name lock for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
}
deleteAfterCaseLocksReleased();
releaseInputDirectoryLocks();
deleteAfterAllLocksReleased();
progress.start(Bundle.DeleteCaseTask_progress_startMessage());
logger.log(Level.INFO, String.format("Beginning deletion of %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
deleteCase();
logger.log(Level.SEVERE, String.format("Deletion of %s (%s) in %s completed", caseDisplayName, caseUniqueName, caseDirectoryPath));
} catch (Throwable ex) {
/*
* Unexpected runtime exceptions firewall.
* Unexpected runtime exceptions firewall. This task is designed to
* be able to be run in an executor service thread pool without
* calling get() on the task's Future<Void>, so this ensures that
* such errors do get ignored.
*/
logger.log(Level.SEVERE, String.format("Unexpected error deleting %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
logger.log(Level.INFO, String.format("Unexpected error deleting %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
} finally {
progress.finish();
@ -169,224 +106,392 @@ abstract class DeleteCaseTask implements Runnable {
}
/**
* Deletes the parts of the case that need to be deleted while holding the
* following exclusive locks: case name lock, case directory lock, and all
* input directory locks.
*/
abstract void deleteWhileHoldingAllLocks();
/**
* Deletes the parts of the case that need to be deleted after releasing
* exclusive locks on the case name and case directory, but while still
* holding exclusive locks on all of the input directories.
*/
abstract void deleteAfterCaseLocksReleased();
/**
* Deletes the parts of the case that need to be deleted after all of the
* locks are released.
*/
abstract void deleteAfterAllLocksReleased();
/**
* Deletes the auto ingest job input directories for the case. Should only
* be called when holding all of the exclusive locks for the case.
* Deletes part or all of the given case.
*/
@NbBundle.Messages({
"DeleteCaseTask.progress.deletingInputDirs=Deleting input directory",
"# {0} - input directory name", "DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}"
"DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...",
"DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock...",
"DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock...",
"DeleteCaseTask.progress.gettingJobNodeData=Getting node data for auto ingest jobs...",
"DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks..."
})
private void deleteCase() {
progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc());
logger.log(Level.INFO, String.format("Connecting to coordination service for deletion of %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
try {
coordinationService = CoordinationService.getInstance();
} catch (CoordinationService.CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to connect to the coordination service, cannot delete %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
}
if (Thread.currentThread().isInterrupted()) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
/*
* Acquire an exclusive case name lock. This is the lock that auto
* ingest nodes acquire exclusively when creating or opening a case
* specified in an auto ingest job manifest file to ensure that only one
* auto ingest node at a time can search the auto ingest output
* directory for an existing case matching the one in the manifest file.
* Acquiring this lock effectively locks auto ingest node job processing
* tasks out of the case to be deleted.
*/
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseNameLock());
logger.log(Level.INFO, String.format("Acquiring an exclusive case name lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath);
try (CoordinationService.Lock nameLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseNameLockNodeName)) {
if (nameLock == null) {
logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case name lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
if (Thread.currentThread().isInterrupted()) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
/*
* Acquire an exclusive case directory lock. A shared case directory
* lock is acquired by any node (auto ingest or examiner) when it
* opens a case and is held by the node for as long as the case is
* open. Acquiring this lock exclusively ensures that no other node
* currently 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 (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
String caseDirLockNodeName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseDirectoryPath);
try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockNodeName)) {
if (caseDirLock == null) {
logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
if (Thread.currentThread().isInterrupted()) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
progress.progress(Bundle.DeleteCaseTask_progress_gettingJobNodeData());
logger.log(Level.INFO, String.format("Fetching auto ingest job node data for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
try {
getAutoIngestJobNodeData();
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error fetching auto ingest job node data for %s (%s) in %s, cannot delete case", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
} catch (InterruptedException ex) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
}
if (Thread.currentThread().isInterrupted()) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
if (!nodeDataForAutoIngestJobs.isEmpty()) {
progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks());
logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
getManifestFileLocks();
if (manifestFileLocks.isEmpty()) {
logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
} else {
logger.log(Level.INFO, String.format("No auto ingest job node data found for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
}
if (Thread.currentThread().isInterrupted()) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath));
releaseManifestFileLocks();
return;
}
try {
deleteWhileHoldingAllLocks();
} catch (InterruptedException ex) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
}
releaseManifestFileLocks();
try {
deleteAfterManifestLocksReleased();
} catch (InterruptedException ex) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
}
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error acquiring exclusive case directory lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
if (Thread.currentThread().isInterrupted()) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath));
return;
}
try {
deleteAfterCaseDirectoryLockReleased();
} catch (InterruptedException ex) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
}
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error acquiring exclusive case name lock for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
try {
deleteAfterCaseNameLockReleased();
} catch (InterruptedException ex) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
return;
}
}
/**
* Deletes the parts of the case that need to be deleted while holding all
* of the exclusive locks: the case name lock, the case directory lock, amd
* the manifest file locks. Note that the locks are acquired in that order
* and released in the opposite order.
*/
abstract void deleteWhileHoldingAllLocks() throws InterruptedException;
/**
* Deletes the parts of the case that need to be deleted after the release
* of the exclusive manifest file locks, while still holding the exclusive
* case name and case directory locks; the manifest file locks are the first
* locks released.
*/
abstract void deleteAfterManifestLocksReleased() throws InterruptedException;
/**
* Deletes the parts of the case that need to be deleted after the release
* of the exclusive manifest file locks and case directory lock, while still
* holding the exclusive case name; the case name lock is the last lock
* released.
*/
abstract void deleteAfterCaseDirectoryLockReleased() throws InterruptedException;
/**
* Deletes the parts of the case that need to be deleted after the release
* of all of the exclusive locks; the case name lock is the last lock
* released.
*/
abstract void deleteAfterCaseNameLockReleased() throws InterruptedException;
/**
* Deletes the auto ingest job input directories for the case. Intended to
* be called by subclasses, if required, in their customization of the
* deleteWhileHoldingAllLocks step of the case deletion algorithm.
*/
@NbBundle.Messages({
"# {0} - input directory name", "DeleteCaseTask.progress.deletingInputDir=Deleting input directory {0}..."
})
protected void deleteInputDirectories() {
progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDirs());
logger.log(Level.INFO, String.format("Deleting input directories for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
boolean allInputDirsDeleted = true;
for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) {
final Path inputDirPath = jobNodeData.getManifestFilePath().getParent();
Path inputDirPath = jobNodeData.getManifestFilePath().getParent();
File inputDir = inputDirPath.toFile();
if (inputDir.exists()) {
progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDir(inputDirPath));
logger.log(Level.INFO, String.format("Deleting input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
if (FileUtil.deleteDir(new File(inputDirPath.toString()))) {
logger.log(Level.WARNING, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
// RJCTODO: Update deletion flags
logger.log(Level.INFO, String.format("Deleting input directory %s for %s (%s) in %s", inputDirPath, caseDisplayName, caseUniqueName, caseDirectoryPath));
if (!FileUtil.deleteDir(new File(inputDirPath.toString()))) {
logger.log(Level.WARNING, String.format("Failed to delete the input directory %s for %s (%s) in %s", inputDirPath, caseDisplayName, caseUniqueName, caseDirectoryPath));
allInputDirsDeleted = false;
}
}
}
if (allInputDirsDeleted) {
setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES);
}
}
/**
* Deletes the case directory, the case database, and the text index for the
* case. Should only be called when holding all of the exclusive locks for
* the case.
* Deletes the case database, the text index, and the case directory for the
* case. Intended to be called by subclasses, if required, in their
* customization of the deleteWhileHoldingAllLocks step of the case deletion
* algorithm.
*/
@NbBundle.Messages({
"DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file",
"DeleteCaseTask.progress.deletingCaseOutput=Deleting case output"
"DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...",
"DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory...",
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job log lock node..."
})
protected void deleteCaseOutput() {
progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile());
logger.log(Level.INFO, String.format("Locating metadata file for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
String metadataFilePath = null;
final File caseDirectory = caseNodeData.getDirectory().toFile();
logger.log(Level.INFO, String.format("Locating metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
CaseMetadata caseMetadata = null;
final File caseDirectory = caseDirectoryPath.toFile();
final File[] filesInDirectory = caseDirectory.listFiles();
if (filesInDirectory != null) {
for (File file : filesInDirectory) {
if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
metadataFilePath = file.getPath();
}
}
}
if (metadataFilePath != null) {
progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseOutput());
logger.log(Level.INFO, String.format("Deleting output for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
try {
Case.deleteCase(new CaseMetadata(Paths.get(metadataFilePath)), false, progress);
} catch (CaseMetadata.CaseMetadataException | CaseActionException ex) {
// RJCTODO: Set delete flags?
logger.log(Level.WARNING, String.format("Error deleting output for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
caseMetadata = new CaseMetadata(Paths.get(file.getPath()));
} catch (CaseMetadata.CaseMetadataException ex) {
logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
break;
}
} else {
logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
// RJCTODO: Set delete flags
}
}
/**
* Deletes the case name, case directory, case resources, and case auto
* ingest log for the case. Should only be called when holding the exclusive
* locks for all of the input directories for the case.
*/
@Messages({
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting auto ingest job log lock node",
"DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources lock node",
"DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock node",
"DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node"
})
protected void deleteCaseLockNodes() {
if (caseMetadata != null) {
progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseOutput());
logger.log(Level.INFO, String.format("Deleting output for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress); // RJCTODO: Make this method throw the interrupted exception.
} else {
logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
}
progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode());
logger.log(Level.INFO, String.format("Deleting case auto ingest job log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
Path logFilePath = AutoIngestJobLogger.getLogPath(caseNodeData.getDirectory());
logger.log(Level.INFO, String.format("Deleting case auto ingest job log lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
Path logFilePath = AutoIngestJobLogger.getLogPath(caseDirectoryPath); //RJCTODO: USe util here
try {
coordinationService.deleteNode(CategoryNode.CASES, logFilePath.toString());
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Error deleting auto ingest job log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
// RJCTODO: Set delete flags
}
progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode());
logger.log(Level.INFO, String.format("Deleting case resources log lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;
try {
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Error deleting case resources lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
// RJCTODO: Set delete flags
}
progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode());
logger.log(Level.INFO, String.format("Deleting case directory lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
final Path caseDirectoryPath = caseNodeData.getDirectory();
try {
coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryPath.toString());
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
// RJCTODO: Set delete flags
}
progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode());
logger.log(Level.INFO, String.format("Deleting case name lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
final String caseNameLockName = TimeStampUtils.removeTimeStamp(caseNodeData.getName());
try {
coordinationService.deleteNode(CategoryNode.CASES, caseNameLockName);
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
// RJCTODO: Set delete flags
logger.log(Level.WARNING, String.format("Error deleting case auto ingest job log lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
} catch (InterruptedException ex) {
logger.log(Level.INFO, String.format("Deletion of %s (%s) in %s cancelled", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
}
/**
* Deletes the input directory lock nodes for the case. Should only be
* called after releasing all of the locks for the case.
* Deletes the manifest file lock coordination service nodes for the case.
* Intended to be called by subclasses, if required, in their customization
* of the deleteAfterManifestLocksReleased step of the case deletion
* algorithm.
*/
@Messages({
"DeleteCaseTask.progress.deletingInputDirLockNodes=Deleting input directory lock nodes"
"DeleteCaseTask.progress.deletingManifestFileLockNodes=Deleting manifest file lock nodes..."
})
protected void deleteInputDirectoryLockNodes() {
progress.progress(Bundle.DeleteCaseTask_progress_deletingInputDirLockNodes());
logger.log(Level.INFO, String.format("Deleting input directory lock nodes for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
protected void deleteManifestFileLockNodes() throws InterruptedException {
boolean allInputDirsDeleted = true;
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileLockNodes());
logger.log(Level.INFO, String.format("Deleting manifest file lock nodes for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath));
for (AutoIngestJobNodeData jobNodeData : nodeDataForAutoIngestJobs) {
try {
logger.log(Level.INFO, String.format("Deleting manifest file lock node for %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
logger.log(Level.INFO, String.format("Deleting manifest file lock node for %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseDisplayName, caseUniqueName, caseDirectoryPath));
coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, jobNodeData.getManifestFilePath().toString());
} catch (CoordinationServiceException ex) {
Path inputDirPath = jobNodeData.getManifestFilePath().getParent();
logger.log(Level.WARNING, String.format("Error deleting input directory lock node %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
// RJCTODO: Set delete flags
logger.log(Level.WARNING, String.format("Error deleting manifest file lock node %s for %s (%s) in %s", jobNodeData.getManifestFilePath(), caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
allInputDirsDeleted = false;
}
}
if (allInputDirsDeleted) {
setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_LOCK_NODES);
}
// RJCTODO: Expand case type in case metadata to include auto ingest cases.
// Disable delete menu item for auto ingest cases, and possibly also add data source
// capability.
}
/**
* Deletes the case directory coordination service lock node for the case.
* Intended to be called by subclasses, if required, in their customization
* of the deleteAfterCaseDirectoryLockReleased step of the case deletion
* algorithm.
*/
@Messages({
"DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node..."
})
protected void deleteCaseDirectoryLockNode() throws InterruptedException {
progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode());
try {
Case.deleteCaseDirectoryLockNode(caseNodeData, progress); // RJCTODO: Case does not need to expose this?
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
}
/**
* Fetches all of the auto ingest job (manifest file) lock coordination
* service node data for a case.
* Deletes the case name coordination service lock node for the case.
* Intended to be called by subclasses, if required, in their customization
* of the deleteAfterCaseNameLockReleased step of the case deletion
* algorithm.
*
* @param caseName The name of the case.
*
* @return A list of auto ingest job (manifest file) lock node data for the
* case.
*
* @throws CoordinationServiceException If there is an error getting a list
* of the auto ingest job (manifest
* file) lock nodes from the
* coordination service.
* @throws InterruptedException
*/
private void getAutoIngestJobNodeData() throws CoordinationServiceException {
String caseName = caseNodeData.getName();
final List<String> nodes = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS);
for (String nodeName : nodes) {
@Messages({
"DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock node..." // RJCTODO: Use consistent terminology
})
protected void deleteCaseNameLockNode() throws InterruptedException {
progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode());
try {
String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseLockName(caseDirectoryPath);
coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName);
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s (%s) in %s", caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
}
/**
* Fetches the auto ingest job data from the manifest file lock coordination
* service nodes for a case.
*
* @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.
*/
private void getAutoIngestJobNodeData() throws CoordinationServiceException, InterruptedException {
String caseName = caseDisplayName;
final List<String> nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS);
for (String nodeName : nodeNames) {
try {
byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName);
if (nodeBytes == null || nodeBytes.length <= 0) {
logger.log(Level.WARNING, String.format("Missing auto ingest job node data for manifest file lock node %s, deleting node", nodeName));
try {
coordinationService.deleteNode(CategoryNode.MANIFESTS, nodeName);
} catch (CoordinationServiceException ex) {
logger.log(Level.WARNING, String.format("Failed to delete empty manifest file lock node %s", nodeName));
}
continue;
}
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes);
if (caseName.equals(nodeData.getCaseName())) {
nodeDataForAutoIngestJobs.add(nodeData);
}
} catch (CoordinationService.CoordinationServiceException | InterruptedException | InvalidDataException ex) {
logger.log(Level.WARNING, String.format("Failed to get coordination service node data for %s", nodeName), ex);
} catch (CoordinationService.CoordinationServiceException | InvalidDataException ex) {
logger.log(Level.WARNING, String.format("Failed to get auto ingest job node data for %s", nodeName), ex);
}
}
}
/**
* Acquires either all or none of the input directory locks for a case.
*
* @param caseNodeData The case node data from the case
* directory lock node for the case.
* @param autoIngestJobNodeDataList The auto ingest job node data from the
* input directory lock nodes for the case.
*
* @return A mapping of manifest file paths to input directory lcoks for all
* input directories for the case; will be empty if all of the locks
* could not be obtained.
* Acquires either all or none of the manifest file locks for a case.
*/
@NbBundle.Messages({
"# {0} - input directory name", "DeleteCaseTask.progress.lockingInputDir=Acquiring exclusive lock on input directory {0}",})
private void getInputDirectoryLocks() {
"# {0} - manifest file name", "DeleteCaseTask.progress.lockingManifestFile=Acquiring exclusive lock on manifest {0}..."
})
private void getManifestFileLocks() {
for (AutoIngestJobNodeData autoIngestJobNodeData : nodeDataForAutoIngestJobs) {
final Path inputDirPath = autoIngestJobNodeData.getManifestFilePath().getParent();
String manifestPath = autoIngestJobNodeData.getManifestFilePath().toString();
try {
progress.progress(Bundle.DeleteCaseTask_progress_lockingInputDir(inputDirPath));
final CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, autoIngestJobNodeData.getManifestFilePath().toString());
progress.progress(Bundle.DeleteCaseTask_progress_lockingManifestFile(manifestPath));
logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath));
CoordinationService.Lock inputDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath);
if (null != inputDirLock) {
manifestFileLocks.put(autoIngestJobNodeData.getManifestFilePath(), inputDirLock);
manifestFileLocks.put(manifestPath, inputDirLock);
} else {
logger.log(Level.WARNING, String.format("Failed to exclusively lock the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()));
releaseInputDirectoryLocks();
logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath));
releaseManifestFileLocks();
manifestFileLocks.clear();
break;
}
} catch (CoordinationService.CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to exclusively lock the input directory %s for %s (%s) in %s", inputDirPath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
releaseInputDirectoryLocks();
logger.log(Level.SEVERE, String.format("Error exclusively locking the manifest %s for %s (%s) in %s", manifestPath, caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
releaseManifestFileLocks();
manifestFileLocks.clear();
break;
}
}
}
@ -396,23 +501,36 @@ abstract class DeleteCaseTask implements Runnable {
* for the case.
*/
@NbBundle.Messages({
"DeleteCaseTask.progress.releasingManifestLocks=Acquiring exclusive manifest file locks",
"# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}"
"# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}..."
})
private void releaseInputDirectoryLocks() {
private void releaseManifestFileLocks() {
if (!manifestFileLocks.isEmpty()) {
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLocks());
for (Map.Entry<Path, CoordinationService.Lock> entry : manifestFileLocks.entrySet()) {
final Path manifestFilePath = entry.getKey();
final CoordinationService.Lock manifestFileLock = entry.getValue();
for (Map.Entry<String, CoordinationService.Lock> entry : manifestFileLocks.entrySet()) {
String manifestFilePath = entry.getKey();
CoordinationService.Lock manifestFileLock = entry.getValue();
try {
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath));
logger.log(Level.INFO, String.format("Releasing the exclusive lock on the manifest file %s for %s (%s) in %s", manifestFilePath, caseDisplayName, caseUniqueName, caseDirectoryPath));
manifestFileLock.release();
} catch (CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Error re3leasing exclusive lock on %s for %s (%s) in %s", manifestFilePath, caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
logger.log(Level.SEVERE, String.format("Error releasing exclusive lock on the manifest file %s for %s (%s) in %s", manifestFilePath, caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
}
}
}
/**
* Sets a deleted item flag for the case.
*
* @param flag The flag to set.
*/
private void setDeletedItemFlag(CaseNodeData.DeletedFlags flag) {
try {
caseNodeData.setDeletedFlag(flag);
coordinationService.setNodeData(CategoryNode.CASES, caseDirectoryPath.toString(), caseNodeData.toArray());
} catch (IOException | CoordinationServiceException | InterruptedException ex) {
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseDisplayName, caseUniqueName, caseDirectoryPath), ex);
}
}
}