mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Interim check in of improved case deletion
This commit is contained in:
parent
db296e64a0
commit
25d4554270
@ -32,6 +32,8 @@ Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0
|
|||||||
Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}.
|
Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}.
|
||||||
# {0} - exception message
|
# {0} - exception message
|
||||||
Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}.
|
Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}.
|
||||||
|
# {0} - case display name
|
||||||
|
Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled.
|
||||||
Case.exceptionMessage.emptyCaseDir=Must specify a case directory path.
|
Case.exceptionMessage.emptyCaseDir=Must specify a case directory path.
|
||||||
Case.exceptionMessage.emptyCaseName=Must specify a case name.
|
Case.exceptionMessage.emptyCaseName=Must specify a case name.
|
||||||
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details.
|
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the log for details.
|
||||||
|
@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
|||||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseCoordinationServiceUtils;
|
||||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||||
import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction;
|
import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction;
|
||||||
import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction;
|
import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction;
|
||||||
@ -140,8 +141,7 @@ public class Case {
|
|||||||
private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
|
private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
|
||||||
private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
|
private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
|
||||||
private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
|
private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
|
||||||
private static final String RESOURCES_LOCK_SUFFIX = "_resources"; //NON-NLS
|
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
|
||||||
private static final String AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt";
|
|
||||||
private static final Logger logger = Logger.getLogger(Case.class.getName());
|
private static final Logger logger = Logger.getLogger(Case.class.getName());
|
||||||
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
|
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
|
||||||
private static final Object caseActionSerializationLock = new Object();
|
private static final Object caseActionSerializationLock = new Object();
|
||||||
@ -1022,13 +1022,16 @@ public class Case {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
deleteCaseResourcesLockNode(caseNodeData, progressIndicator);
|
deleteCaseResourcesLockNode(caseNodeData, progressIndicator);
|
||||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
} catch (CoordinationServiceException ex) {
|
||||||
|
if (!isNoNodeException(ex)) {
|
||||||
errorsOccurred = true;
|
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);
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// RJCTODO: Is this behavior implemented correctly?
|
// RJCTODO: Is this behavior implemented correctly?
|
||||||
|
|
||||||
} catch (CoordinationServiceException ex) {
|
} catch (CoordinationServiceException ex) {
|
||||||
logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
|
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());
|
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
|
||||||
@ -1071,7 +1074,8 @@ public class Case {
|
|||||||
* during a wait.
|
* during a wait.
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
|
public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata,
|
||||||
|
ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
|
||||||
boolean errorsOccurred = false;
|
boolean errorsOccurred = false;
|
||||||
try {
|
try {
|
||||||
deleteCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
|
deleteCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
|
||||||
@ -1201,9 +1205,6 @@ public class Case {
|
|||||||
"Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
|
"Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
|
||||||
})
|
})
|
||||||
private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
|
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());
|
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
|
||||||
logger.log(Level.INFO, String.format("Deleting case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
logger.log(Level.INFO, String.format("Deleting case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
|
||||||
if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
|
if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
|
||||||
@ -1273,7 +1274,7 @@ public class Case {
|
|||||||
@Beta
|
@Beta
|
||||||
public static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException {
|
public static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException {
|
||||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode());
|
progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode());
|
||||||
String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;//RJCTODO: Use utility
|
String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory());
|
||||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||||
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
|
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
|
||||||
}
|
}
|
||||||
@ -1306,6 +1307,24 @@ public class Case {
|
|||||||
coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath);
|
coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Examines a coordination service exception to try to determine if it is a
|
||||||
|
* no node exception.
|
||||||
|
*
|
||||||
|
* @param ex A coordination service exception.
|
||||||
|
*
|
||||||
|
* @return True or false.
|
||||||
|
*/
|
||||||
|
private static boolean isNoNodeException(CoordinationServiceException ex) {
|
||||||
|
boolean isNodeNodeEx = false;
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
if (cause != null) {
|
||||||
|
String causeMessage = cause.getMessage();
|
||||||
|
isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
|
||||||
|
}
|
||||||
|
return isNodeNodeEx;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a deleted item flag in the coordination service node data for a
|
* Sets a deleted item flag in the coordination service node data for a
|
||||||
* multi-user case.
|
* multi-user case.
|
||||||
@ -1342,7 +1361,8 @@ public class Case {
|
|||||||
})
|
})
|
||||||
private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
|
private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
|
||||||
try {
|
try {
|
||||||
String resourcesNodeName = caseDir + RESOURCES_LOCK_SUFFIX;
|
Path caseDirPath = Paths.get(caseDir);
|
||||||
|
String resourcesNodeName = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseDirPath);
|
||||||
Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
||||||
return lock;
|
return lock;
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
|
@ -139,6 +139,29 @@ public final class CaseMetadata {
|
|||||||
return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
|
return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate the case meta data file in the supplied directory. If the file
|
||||||
|
* does not exist, null is returned.
|
||||||
|
*
|
||||||
|
* @param directoryPath Directory path to search
|
||||||
|
*
|
||||||
|
* @return case meta data file path or null
|
||||||
|
*/
|
||||||
|
// RJCTODO: Perhaps this should return a CaseMetadata object
|
||||||
|
// RJCTODO: It should say get...path
|
||||||
|
public static Path getCaseMetadataFile(Path directoryPath) {
|
||||||
|
final File[] files = directoryPath.toFile().listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
final String fileName = file.getName().toLowerCase();
|
||||||
|
if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
|
||||||
|
return file.toPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a CaseMetadata object for a new case. The metadata is not
|
* Constructs a CaseMetadata object for a new case. The metadata is not
|
||||||
* persisted to the case metadata file until writeFile or a setX method is
|
* persisted to the case metadata file until writeFile or a setX method is
|
||||||
@ -191,27 +214,6 @@ public final class CaseMetadata {
|
|||||||
readFromFile();
|
readFromFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Locate the case meta data file in the supplied directory. If the file does
|
|
||||||
* not exist, null is returned.
|
|
||||||
*
|
|
||||||
* @param directoryPath Directory path to search
|
|
||||||
* @return case meta data file path or null
|
|
||||||
*/
|
|
||||||
public static Path getCaseMetadataFile(Path directoryPath) {
|
|
||||||
final File[] caseFiles = directoryPath.toFile().listFiles();
|
|
||||||
if(caseFiles != null) {
|
|
||||||
for (File file : caseFiles) {
|
|
||||||
final String fileName = file.getName().toLowerCase();
|
|
||||||
if (fileName.endsWith(CaseMetadata.getFileExtension())) {
|
|
||||||
return file.toPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the full path to the case metadata file.
|
* Gets the full path to the case metadata file.
|
||||||
*
|
*
|
||||||
|
@ -289,7 +289,7 @@ public final class CaseNodeData {
|
|||||||
CASE_DB(2),
|
CASE_DB(2),
|
||||||
CASE_DIR(4),
|
CASE_DIR(4),
|
||||||
DATA_SOURCES(8),
|
DATA_SOURCES(8),
|
||||||
MANIFEST_FILE_LOCK_NODES(16);
|
MANIFEST_FILE_NODES(16);
|
||||||
|
|
||||||
private final short value;
|
private final short value;
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Delet
|
|||||||
MultiUserCaseBrowserCustomizer.column.directory=Directory
|
MultiUserCaseBrowserCustomizer.column.directory=Directory
|
||||||
MultiUserCaseBrowserCustomizer.column.displayName=Name
|
MultiUserCaseBrowserCustomizer.column.displayName=Name
|
||||||
MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time
|
MultiUserCaseBrowserCustomizer.column.lastAccessTime=Last Access Time
|
||||||
MultiUserCaseBrowserCustomizer.column.manifestCoordSvcNodesDeleteStatus=Manifest ZooKeeper Node Deleted
|
MultiUserCaseBrowserCustomizer.column.manifestFileZNodesDeleteStatus=Manifest Znodes Deleted
|
||||||
MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted
|
MultiUserCaseBrowserCustomizer.column.textIndexDeleteStatus=Text Index Deleted
|
||||||
|
MultiUserCaseNode.column.createTime=False
|
||||||
|
MultiUserCaseNode.columnValue.true=True
|
||||||
MultiUserCasesBrowserPanel.waitNode.message=Please Wait...
|
MultiUserCasesBrowserPanel.waitNode.message=Please Wait...
|
||||||
|
@ -78,7 +78,7 @@ final class MultiUserCaseNode extends AbstractNode {
|
|||||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, caseNodeData.getLastAccessDate()));
|
sheetSet.put(new NodeProperty<>(propName, propName, propName, caseNodeData.getLastAccessDate()));
|
||||||
break;
|
break;
|
||||||
case MANIFEST_FILE_ZNODES_DELETE_STATUS:
|
case MANIFEST_FILE_ZNODES_DELETE_STATUS:
|
||||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_LOCK_NODES)));
|
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_NODES)));
|
||||||
break;
|
break;
|
||||||
case DATA_SOURCES_DELETE_STATUS:
|
case DATA_SOURCES_DELETE_STATUS:
|
||||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.DATA_SOURCES)));
|
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.DATA_SOURCES)));
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011-2017 Basis Technology Corp.
|
* Copyright 2013-2019 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -59,7 +59,19 @@ public final class ExecUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process terminator that can be used to kill a processes after it exceeds
|
* A process terminator that can be used to kill a process spawned by a
|
||||||
|
* thread that has been interrupted.
|
||||||
|
*/
|
||||||
|
public static class InterruptedThreadProcessTerminator implements ProcessTerminator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldTerminateProcess() {
|
||||||
|
return Thread.currentThread().isInterrupted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process terminator that can be used to kill a process after it exceeds
|
||||||
* a maximum allowable run time.
|
* a maximum allowable run time.
|
||||||
*/
|
*/
|
||||||
public static class TimedProcessTerminator implements ProcessTerminator {
|
public static class TimedProcessTerminator implements ProcessTerminator {
|
||||||
@ -212,9 +224,6 @@ public final class ExecUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* EVERYTHING FOLLOWING THIS LINE IS DEPRECATED AND SLATED FOR REMOVAL
|
|
||||||
*/
|
|
||||||
private static final Logger logger = Logger.getLogger(ExecUtil.class.getName());
|
private static final Logger logger = Logger.getLogger(ExecUtil.class.getName());
|
||||||
private Process proc = null;
|
private Process proc = null;
|
||||||
private ExecUtil.StreamToStringRedirect errorStringRedirect = null;
|
private ExecUtil.StreamToStringRedirect errorStringRedirect = null;
|
||||||
|
@ -1211,6 +1211,18 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
|
|||||||
*/
|
*/
|
||||||
try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), INPUT_SCAN_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES)) {
|
try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), INPUT_SCAN_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES)) {
|
||||||
if (null != manifestLock) {
|
if (null != manifestLock) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that the lock has been acquired, make sure the
|
||||||
|
* manifest is still here. This is a way to resolve the
|
||||||
|
* race condition between this task and case deletion
|
||||||
|
* tasks without resorting to a protocol using locking
|
||||||
|
* of the input directory.
|
||||||
|
*/
|
||||||
|
if (!filePath.toFile().exists()) {
|
||||||
|
return CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString());
|
byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString());
|
||||||
if (null != rawData && rawData.length > 0) {
|
if (null != rawData && rawData.length > 0) {
|
||||||
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData);
|
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData);
|
||||||
|
@ -8,9 +8,6 @@ AinStatusNode.status.shuttingdown=Shutting Down
|
|||||||
AinStatusNode.status.startingup=Starting Up
|
AinStatusNode.status.startingup=Starting Up
|
||||||
AinStatusNode.status.title=Status
|
AinStatusNode.status.title=Status
|
||||||
AinStatusNode.status.unknown=Unknown
|
AinStatusNode.status.unknown=Unknown
|
||||||
ArchiveDSP.dsType.text=Archive file
|
|
||||||
ArchiveFilePanel.moduleErr=Module Error
|
|
||||||
ArchiveFilePanel.moduleErr.msg=A module caused an error listening to ArchiveFilePanel updates. See log to determine which module. Some data could be incomplete.\n
|
|
||||||
AutoIngestAdminActions.cancelJobAction.title=Cancel Job
|
AutoIngestAdminActions.cancelJobAction.title=Cancel Job
|
||||||
AutoIngestAdminActions.cancelModuleAction.title=Cancel Module
|
AutoIngestAdminActions.cancelModuleAction.title=Cancel Module
|
||||||
AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case.
|
AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case.
|
||||||
@ -169,43 +166,44 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard
|
|||||||
CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs
|
CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs
|
||||||
CTL_CasesDashboardAction=Multi-User Cases Dashboard
|
CTL_CasesDashboardAction=Multi-User Cases Dashboard
|
||||||
CTL_CasesDashboardTopComponent=Cases
|
CTL_CasesDashboardTopComponent=Cases
|
||||||
DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case.
|
DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)
|
||||||
DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on "C:" drive
|
|
||||||
DeleteCaseInputAction.menuItemText=Delete Input
|
DeleteCaseInputAction.menuItemText=Delete Input
|
||||||
DeleteCaseInputAction.progressDisplayName=Delete Input
|
DeleteCaseInputAction.progressDisplayName=Delete Input
|
||||||
DeleteCaseInputAction.taskName=input
|
DeleteCaseInputAction.taskName=input
|
||||||
|
DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes
|
||||||
DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output
|
DeleteCaseInputAndOutputAction.menuItemText=Delete Input and Output
|
||||||
DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output
|
DeleteCaseInputAndOutputAction.progressDisplayName=Delete Input and Output
|
||||||
DeleteCaseInputAndOutputAction.taskName=input-and-output
|
DeleteCaseInputAndOutputAction.taskName=input-and-output
|
||||||
|
DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes
|
||||||
DeleteCaseOutputAction.menuItemText=Delete Output
|
DeleteCaseOutputAction.menuItemText=Delete Output
|
||||||
DeleteCaseOutputAction.progressDisplayName=Delete Output
|
DeleteCaseOutputAction.progressDisplayName=Delete Output
|
||||||
DeleteCaseOutputAction.taskName=output
|
DeleteCaseOutputAction.taskName=output
|
||||||
DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock...
|
DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...
|
||||||
DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock...
|
DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...
|
||||||
DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks
|
|
||||||
DeleteCaseTask.progress.acquiringManifestFileLocks=Acquiring exclusive manifest file locks...
|
|
||||||
DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...
|
DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...
|
||||||
DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...
|
DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...
|
||||||
DeleteCaseTask.progress.deletingCaseOutput=Deleting case database, text index, and directory...
|
DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...
|
||||||
|
DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode...
|
||||||
DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...
|
DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...
|
||||||
# {0} - input directory name
|
DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...
|
||||||
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 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...
|
|
||||||
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
|
# {0} - manifest file path
|
||||||
DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}...
|
DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}...
|
||||||
DeleteCaseTask.progress.releasingManifestLocks=Releasing exclusive manifest file locks...
|
# {0} - manifest file path
|
||||||
DeleteCaseTask.progress.startMessage=Preparing for deletion...
|
DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}...
|
||||||
GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz)
|
DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node...
|
||||||
|
DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode...
|
||||||
|
DeleteCaseTask.Progress.gettingManifestPaths=Getting manifest file paths...
|
||||||
|
DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths...
|
||||||
|
DeleteCaseTask.progress.locatingCaseMetadataFile=Opening case metadata file...
|
||||||
|
# {0} - manifest file path
|
||||||
|
DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}...
|
||||||
|
DeleteCaseTask.progress.openingCaseDatabase=Opening the case database...
|
||||||
|
DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file...
|
||||||
|
# {0} - manifest file path
|
||||||
|
DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}...
|
||||||
|
# {0} - manifest file path
|
||||||
|
DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}...
|
||||||
|
DeleteCaseTask.progress.startMessage=Starting deletion...
|
||||||
HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases
|
HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases
|
||||||
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.
|
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.
|
||||||
OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.
|
OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.
|
||||||
@ -406,10 +404,6 @@ AutoIngestMetricsDialog.reportTextArea.text=
|
|||||||
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
|
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
|
||||||
AutoIngestMetricsDialog.closeButton.text=Close
|
AutoIngestMetricsDialog.closeButton.text=Close
|
||||||
AutoIngestMetricsDialog.datePicker.toolTipText=Choose a date
|
AutoIngestMetricsDialog.datePicker.toolTipText=Choose a date
|
||||||
ArchiveFilePanel.pathLabel.text=Browse for an archive file:
|
|
||||||
ArchiveFilePanel.browseButton.text=Browse
|
|
||||||
ArchiveFilePanel.pathTextField.text=
|
|
||||||
ArchiveFilePanel.errorLabel.text=Error Label
|
|
||||||
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date:
|
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date:
|
||||||
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
|
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
|
||||||
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job
|
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job
|
||||||
|
@ -18,9 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||||
|
|
||||||
@ -52,6 +54,16 @@ final class DeleteCaseInputAction extends DeleteCaseAction {
|
|||||||
super(Bundle.DeleteCaseInputAction_menuItemText(), Bundle.DeleteCaseInputAction_progressDisplayName(), Bundle.DeleteCaseInputAction_taskName());
|
super(Bundle.DeleteCaseInputAction_menuItemText(), Bundle.DeleteCaseInputAction_progressDisplayName(), Bundle.DeleteCaseInputAction_taskName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)"
|
||||||
|
})
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseInputAction_confirmationText())) {
|
||||||
|
super.actionPerformed(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress);
|
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress);
|
||||||
|
@ -18,8 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.NbBundle.Messages;
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||||
|
|
||||||
@ -46,9 +49,19 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction {
|
|||||||
super(Bundle.DeleteCaseInputAndOutputAction_menuItemText(), Bundle.DeleteCaseInputAndOutputAction_progressDisplayName(), Bundle.DeleteCaseInputAndOutputAction_taskName());
|
super(Bundle.DeleteCaseInputAndOutputAction_menuItemText(), Bundle.DeleteCaseInputAndOutputAction_progressDisplayName(), Bundle.DeleteCaseInputAndOutputAction_taskName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest files\n-Data sources\n-Input directories (if empty)\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes"
|
||||||
|
})
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseInputAndOutputAction_confirmationText())) {
|
||||||
|
super.actionPerformed(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress);
|
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_ALL, progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||||
|
|
||||||
@ -50,6 +52,16 @@ final class DeleteCaseOutputAction extends DeleteCaseAction {
|
|||||||
super(Bundle.DeleteCaseOutputAction_menuItemText(), Bundle.DeleteCaseOutputAction_progressDisplayName(), Bundle.DeleteCaseOutputAction_taskName());
|
super(Bundle.DeleteCaseOutputAction_menuItemText(), Bundle.DeleteCaseOutputAction_progressDisplayName(), Bundle.DeleteCaseOutputAction_taskName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes"
|
||||||
|
})
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteCaseOutputAction_confirmationText())) {
|
||||||
|
super.actionPerformed(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress);
|
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress);
|
||||||
|
@ -23,14 +23,17 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.openide.util.Exceptions;
|
|
||||||
import org.openide.util.Lookup;
|
import org.openide.util.Lookup;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.NbBundle.Messages;
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||||
@ -39,62 +42,53 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
|||||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
|
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
|
||||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
|
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
|
||||||
|
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||||
|
import org.sleuthkit.autopsy.core.UserPreferencesException;
|
||||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException;
|
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException;
|
||||||
|
import org.sleuthkit.datamodel.DataSource;
|
||||||
|
import org.sleuthkit.datamodel.Image;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task that deletes part or all of a given case. Note that all logging done
|
* A task that deletes part or all of a given case. Note that all logging is
|
||||||
* by this task is directed to the dedicated auto ingest dashboard log instead
|
* directed to the dedicated auto ingest dashboard log instead of to the general
|
||||||
* of to the general application log.
|
* application log.
|
||||||
*/
|
*/
|
||||||
// RJCTODO:
|
|
||||||
// 1. Expand case type in case metadata to include auto ingest cases.
|
|
||||||
// Disable the delete menu item in the main app menu for auto ingest cases,
|
|
||||||
// and possibly also use this to delete the add data source capability. Could use
|
|
||||||
// this to limit the display of nodes in the in the auto ingest cases dashboard.
|
|
||||||
// 2. When an instance of this class finishes, publish an event via event bus
|
|
||||||
// so that the case browser can refresh.
|
|
||||||
// 3. Add code to file deletion utilities such that on Wimdows, for paths
|
|
||||||
// exceeding 255 chars, robocopy is invoked for the deletion. Make the new file
|
|
||||||
// deletion utility throw exceptions instead of return a boolean result code.
|
|
||||||
// 4. Make other dashbaord use the dashboard logger.
|
|
||||||
// 5. Consider moving all of the dashboard code into its own autoingest.dashboard package.
|
|
||||||
// 6. AutoIngestManager.addCompletedJob node data version updating might be out of date.
|
|
||||||
// 7. Deal with cancellation during lock releases. Look at using
|
|
||||||
// https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Uninterruptibles.html
|
|
||||||
// getUninterruptibly to do os.
|
|
||||||
// 8. With the removal of the auto ingest control panel, we can eliminate the
|
|
||||||
// completed jobs list and the processing list from AutoIngestManager.
|
|
||||||
final class DeleteCaseTask implements Runnable {
|
final class DeleteCaseTask implements Runnable {
|
||||||
|
|
||||||
private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 5;
|
private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 5;
|
||||||
|
private static final int MANIFEST_DELETE_TRIES = 3;
|
||||||
|
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
|
||||||
private static final Logger logger = AutoIngestDashboardLogger.getLogger();
|
private static final Logger logger = AutoIngestDashboardLogger.getLogger();
|
||||||
private final CaseNodeData caseNodeData;
|
private final CaseNodeData caseNodeData;
|
||||||
private final DeleteOptions deleteOption;
|
private final DeleteOptions deleteOption;
|
||||||
private final ProgressIndicator progress;
|
private final ProgressIndicator progress;
|
||||||
|
private final List<Path> manifestFilePaths;
|
||||||
private final List<Lock> manifestFileLocks;
|
private final List<Lock> manifestFileLocks;
|
||||||
private CoordinationService coordinationService;
|
private CoordinationService coordinationService;
|
||||||
|
private CaseMetadata caseMetadata;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Options to support implementing differnet case deletion uses cases.
|
* Options to support implementing different case deletion use cases.
|
||||||
*/
|
*/
|
||||||
public enum DeleteOptions {
|
enum DeleteOptions {
|
||||||
/**
|
/**
|
||||||
* Delete the auto ingest job manifests and corresponding data sources,
|
* Delete the auto ingest job manifests and corresponding data sources,
|
||||||
* if any, while leaving the manifest file coordination service nodes
|
* while leaving the manifest file coordination service nodes and the
|
||||||
* and the rest of the case intact. The use case is freeing auto ingest
|
* rest of the case intact. The use case is freeing auto ingest input
|
||||||
* input directory space while retaining the option to restore the data
|
* directory space while retaining the option to restore the data
|
||||||
* sources, effectively restoring the case.
|
* sources, effectively restoring the case.
|
||||||
*/
|
*/
|
||||||
DELETE_INPUT,
|
DELETE_INPUT,
|
||||||
/**
|
/**
|
||||||
* Delete the auto ingest job coordination service nodes, if any, and
|
* Delete the manifest file coordination service nodes and the output
|
||||||
* the output for a case produced via auto ingest, while leaving the
|
* for a case, while leaving the auto ingest job manifests and
|
||||||
* auto ingest job input directories intact. The use case is auto ingest
|
* corresponding data sources intact. The use case is auto ingest
|
||||||
* reprocessing of a case with a clean slate without having to restore
|
* reprocessing of a case with a clean slate without having to restore
|
||||||
* the input directories.
|
* the manifests and data sources.
|
||||||
*/
|
*/
|
||||||
DELETE_OUTPUT,
|
DELETE_OUTPUT,
|
||||||
/**
|
/**
|
||||||
@ -117,7 +111,8 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
this.caseNodeData = caseNodeData;
|
this.caseNodeData = caseNodeData;
|
||||||
this.deleteOption = deleteOption;
|
this.deleteOption = deleteOption;
|
||||||
this.progress = progress;
|
this.progress = progress;
|
||||||
this.manifestFileLocks = new ArrayList<>();
|
manifestFilePaths = new ArrayList<>();
|
||||||
|
manifestFileLocks = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -127,9 +122,9 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
progress.start(Bundle.DeleteCaseTask_progress_startMessage());
|
progress.start(Bundle.DeleteCaseTask_progress_startMessage());
|
||||||
logger.log(Level.INFO, String.format("Starting attempt to delete %s (%s)", caseNodeData.getDisplayName(), deleteOption));
|
logger.log(Level.INFO, String.format("Starting deleting %s (%s)", caseNodeData.getDisplayName(), deleteOption));
|
||||||
deleteCase();
|
deleteCase();
|
||||||
logger.log(Level.INFO, String.format("Finished attempt to delete %s (%s)", caseNodeData.getDisplayName(), deleteOption));
|
logger.log(Level.INFO, String.format("Finished deleting %s (%s)", caseNodeData.getDisplayName(), deleteOption));
|
||||||
|
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
/*
|
/*
|
||||||
@ -139,6 +134,7 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
* the task, so this ensures that any such errors get logged.
|
* the task, so this ensures that any such errors get logged.
|
||||||
*/
|
*/
|
||||||
logger.log(Level.SEVERE, String.format("Unexpected error deleting %s", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.SEVERE, String.format("Unexpected error deleting %s", caseNodeData.getDisplayName()), ex);
|
||||||
|
throw ex;
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
progress.finish();
|
progress.finish();
|
||||||
@ -152,9 +148,13 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
"DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...",
|
"DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...",
|
||||||
"DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...",
|
"DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...",
|
||||||
"DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...",
|
"DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...",
|
||||||
|
"DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths...",
|
||||||
"DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...",
|
"DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...",
|
||||||
"DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...",
|
"DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file...",
|
||||||
"DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node..."
|
"DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode...",
|
||||||
|
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...",
|
||||||
|
"DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...",
|
||||||
|
"DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode..."
|
||||||
})
|
})
|
||||||
private void deleteCase() {
|
private void deleteCase() {
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc());
|
progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc());
|
||||||
@ -165,9 +165,10 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred connecting to the coordination service", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred connecting to the coordination service", caseNodeData.getDisplayName()), ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.log(Level.INFO, String.format("Connected to the coordination service for deletion of %s", caseNodeData.getDisplayName()));
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,9 +191,10 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Could not delete %s because a case name lock was already held by another host", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.log(Level.INFO, String.format("Acquired an exclusive case name lock for %s", caseNodeData.getDisplayName()));
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +207,6 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
* deleted open and prevents another node from trying to open the
|
* deleted open and prevents another node from trying to open the
|
||||||
* case while it is being deleted.
|
* case while it is being deleted.
|
||||||
*/
|
*/
|
||||||
boolean success = true; // RJCTODO: Instead of having this flag, read the casenodedata instead
|
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock());
|
progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock());
|
||||||
logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName()));
|
||||||
String caseDirLockName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory());
|
String caseDirLockName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory());
|
||||||
@ -214,96 +215,191 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Could not delete %s because a case directory lock was already held by another host", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.log(Level.INFO, String.format("Acquired an exclusive case directory lock for %s", caseNodeData.getDisplayName()));
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress.progress(Bundle.DeleteCaseTask_progress_gettingManifestPaths());
|
||||||
|
logger.log(Level.INFO, String.format("Getting manifest file paths for %s", caseNodeData.getDisplayName()));
|
||||||
|
try {
|
||||||
|
getManifestFilePaths();
|
||||||
|
} catch (IOException | CoordinationServiceException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("An error occurred getting the manifest file paths", caseNodeData.getDisplayName()), ex);
|
||||||
|
return;
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifestFilePaths.isEmpty()) {
|
||||||
|
logger.log(Level.INFO, String.format("Obtained manifest file paths for %s", caseNodeData.getDisplayName()));
|
||||||
/*
|
/*
|
||||||
* Acquire exclusive locks for the auto ingest job manifest
|
* Acquire exclusive locks for the auto ingest job manifest
|
||||||
* files for the case, if any. Manifest file locks are acquired
|
* files for the case, if any. Manifest file locks are
|
||||||
* by the auto ingest node (AIN) input directory scanning tasks
|
* acquired by the auto ingest node (AIN) input directory
|
||||||
* when they look for auto ingest jobs to enqueue, and by the
|
* scanning tasks when they look for auto ingest jobs to
|
||||||
* AIN job processing tasks when they execute a job. Acquiring
|
* enqueue, and by the AIN job execution tasks when they do
|
||||||
* these locks here ensures that the scanning tasks and job
|
* a job. Acquiring these locks here ensures that the
|
||||||
* processing tasks cannot do anything with the auto ingest jobs
|
* scanning tasks and job execution tasks cannot do anything
|
||||||
* for a case during case deletion.
|
* with the auto ingest jobs for a case during case
|
||||||
|
* deletion.
|
||||||
*/
|
*/
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks());
|
progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks());
|
||||||
logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
||||||
try {
|
try {
|
||||||
if (!acquireManifestFileLocks()) {
|
if (!acquireManifestFileLocks()) {
|
||||||
logger.log(Level.INFO, String.format("Could not delete %s because a manifest file lock was already held by another host", caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Could not delete %s because at least one manifest file lock was already held by another host", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (CoordinationServiceException ex) {
|
} catch (IOException | CoordinationServiceException ex) {
|
||||||
logger.log(Level.WARNING, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex);
|
||||||
return;
|
return;
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.log(Level.INFO, String.format("Acquired exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
|
||||||
releaseManifestFileLocks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
|
||||||
try {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
|
||||||
deleteAutoIngestInput();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
// RJCTODO:
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
|
||||||
releaseManifestFileLocks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
|
||||||
releaseManifestFileLocks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
|
||||||
try {
|
|
||||||
success = deleteCaseOutput();
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
|
||||||
releaseManifestFileLocks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
|
||||||
releaseManifestFileLocks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
|
||||||
success = deleteManifestFileNodes();
|
|
||||||
} else {
|
} else {
|
||||||
releaseManifestFileLocks();
|
logger.log(Level.INFO, String.format("No manifest file paths found for %s", caseNodeData.getDisplayName()));
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
|
releaseManifestFileLocks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final File caseDirectory = caseNodeData.getDirectory().toFile();
|
||||||
|
if (caseDirectory.exists()) {
|
||||||
|
progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile());
|
||||||
|
logger.log(Level.INFO, String.format("Locating case metadata file for %s", caseNodeData.getDisplayName()));
|
||||||
|
Path caseMetadataPath = CaseMetadata.getCaseMetadataFile(caseNodeData.getDirectory());
|
||||||
|
if (caseMetadataPath != null) {
|
||||||
|
logger.log(Level.INFO, String.format("Found case metadata file for %s", caseNodeData.getDisplayName()));
|
||||||
|
try {
|
||||||
|
caseMetadata = new CaseMetadata(caseMetadataPath);
|
||||||
|
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifestFilePaths.isEmpty() && (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL)) {
|
||||||
|
try {
|
||||||
|
deleteAutoIngestInput();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
||||||
|
try {
|
||||||
|
logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName()));
|
||||||
|
Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger);
|
||||||
|
logger.log(Level.INFO, String.format("Deleted output for %s", caseNodeData.getDisplayName()));
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (CaseMetadata.CaseMetadataException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error reading metadata file for %s", caseNodeData.getDisplayName()), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, String.format("No case metadata file found for %s", caseNodeData.getDisplayName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setDeletedItemFlag(CaseNodeData.DeletedFlags.CASE_DIR);
|
||||||
|
logger.log(Level.INFO, String.format("No case directory found for %s", caseNodeData.getDisplayName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
||||||
|
progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode());
|
||||||
|
logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName()));
|
||||||
|
try {
|
||||||
|
Case.deleteCaseResourcesLockNode(caseNodeData, progress);
|
||||||
|
logger.log(Level.INFO, String.format("Deleted case resources znode for %s", caseNodeData.getDisplayName()));
|
||||||
|
} catch (CoordinationServiceException ex) {
|
||||||
|
if (!isNoNodeException(ex)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error deleting case resources znode for %s", caseNodeData.getDisplayName()), ex);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode());
|
||||||
|
logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()));
|
||||||
|
String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory());
|
||||||
|
try {
|
||||||
|
coordinationService.deleteNode(CategoryNode.CASES, logFilePath);
|
||||||
|
logger.log(Level.INFO, String.format("Deleted case auto ingest job log znode for %s", caseNodeData.getDisplayName()));
|
||||||
|
} catch (CoordinationServiceException ex) {
|
||||||
|
if (!isNoNodeException(ex)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName()), ex);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
||||||
|
try {
|
||||||
|
logger.log(Level.INFO, String.format("Deleting manifest file znodes for %s", caseNodeData.getDisplayName()));
|
||||||
|
deleteManifestFileNodes();
|
||||||
|
logger.log(Level.INFO, String.format("Deleted manifest file znodes for %s", caseNodeData.getDisplayName()));
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseManifestFileLocks();
|
||||||
|
|
||||||
} catch (CoordinationServiceException ex) {
|
} catch (CoordinationServiceException ex) {
|
||||||
logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case directory lock", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the case directory lock", caseNodeData.getDisplayName()), ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,14 +410,20 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
* leave the node so that what was and was not deleted can be
|
* leave the node so that what was and was not deleted can be
|
||||||
* inspected.
|
* inspected.
|
||||||
*/
|
*/
|
||||||
if (success && (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL)) {
|
if ((deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL)
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode());
|
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.DATA_SOURCES)
|
||||||
|
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)
|
||||||
|
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)
|
||||||
|
&& caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES)) {
|
||||||
|
progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode());
|
||||||
|
logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName()));
|
||||||
try {
|
try {
|
||||||
Case.deleteCaseDirectoryLockNode(caseNodeData, progress);
|
Case.deleteCaseDirectoryLockNode(caseNodeData, progress);
|
||||||
|
logger.log(Level.INFO, String.format("Deleted case directory znode for %s", caseNodeData.getDisplayName()));
|
||||||
} catch (CoordinationServiceException ex) {
|
} catch (CoordinationServiceException ex) {
|
||||||
logger.log(Level.WARNING, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.SEVERE, String.format("Error deleting case directory lock node for %s", caseNodeData.getDisplayName()), ex);
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,7 +434,7 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,14 +443,109 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
* service node for it can be deleted if the use case requires it.
|
* service node for it can be deleted if the use case requires it.
|
||||||
*/
|
*/
|
||||||
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingNameLockNode());
|
progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode());
|
||||||
|
logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName()));
|
||||||
try {
|
try {
|
||||||
String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory());
|
String caseNameLockNodeName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory());
|
||||||
coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); // RJCTODO: Should this be a Case method?
|
coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName);
|
||||||
|
logger.log(Level.INFO, String.format("Deleted case name znode for %s", caseNodeData.getDisplayName()));
|
||||||
} catch (CoordinationServiceException ex) {
|
} catch (CoordinationServiceException ex) {
|
||||||
logger.log(Level.WARNING, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.SEVERE, String.format("Error deleting case name lock node for %s", caseNodeData.getDisplayName()), ex);
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the manifest file paths for the case, if there are any.
|
||||||
|
*
|
||||||
|
* @throws CoordinationServiceException If there is an error completing a
|
||||||
|
* coordination service operation.
|
||||||
|
* @throws InterruptedException If the thread in which this task is
|
||||||
|
* running is interrupted while blocked
|
||||||
|
* waiting for a coordination service
|
||||||
|
* operation to complete.
|
||||||
|
* @throws IOException If there is an error reading the
|
||||||
|
* manifests list file.
|
||||||
|
*/
|
||||||
|
private void getManifestFilePaths() throws IOException, CoordinationServiceException, InterruptedException {
|
||||||
|
final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName());
|
||||||
|
final File manifestListsFile = manifestsListFilePath.toFile();
|
||||||
|
if (manifestListsFile.exists()) {
|
||||||
|
getManifestPathsFromFile(manifestsListFilePath);
|
||||||
|
} else {
|
||||||
|
getManifestPathsFromNodes();
|
||||||
|
}
|
||||||
|
if (manifestFilePaths.isEmpty()) {
|
||||||
|
setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of the manifest file paths for the case by reading them from
|
||||||
|
* the manifests list file for the case.
|
||||||
|
*
|
||||||
|
* @param manifestsListFilePath The path of the manifests list file.
|
||||||
|
*
|
||||||
|
* @throws IOException If there is an error reading the manifests
|
||||||
|
* list file.
|
||||||
|
* @throws InterruptedException If the thread in which this task is running
|
||||||
|
* is interrupted while blocked waiting for a
|
||||||
|
* coordination service operation to complete.
|
||||||
|
*/
|
||||||
|
private void getManifestPathsFromFile(Path manifestsListFilePath) throws IOException, InterruptedException {
|
||||||
|
try (final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath)) {
|
||||||
|
while (manifestsListFileScanner.hasNextLine()) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
final Path manifestFilePath = Paths.get(manifestsListFileScanner.nextLine());
|
||||||
|
if (manifestFilePath.toFile().exists()) {
|
||||||
|
manifestFilePaths.add(manifestFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of the manifest file paths for the case by sifting through
|
||||||
|
* the node data of the manifest file coordination service nodes and
|
||||||
|
* matching on case name.
|
||||||
|
*
|
||||||
|
* @throws CoordinationServiceException If there is an error completing a
|
||||||
|
* coordination service operation.
|
||||||
|
* @throws InterruptedException If the thread in which this task is
|
||||||
|
* running is interrupted while blocked
|
||||||
|
* waiting for a coordination service
|
||||||
|
* operation to complete.
|
||||||
|
*/
|
||||||
|
private void getManifestPathsFromNodes() throws CoordinationServiceException, InterruptedException {
|
||||||
|
/*
|
||||||
|
* Get the original, undecorated case name from the case directory. This
|
||||||
|
* is necessary because the case display name can be changed and the
|
||||||
|
* original case name may have a time stamp added to make it unique,
|
||||||
|
* depending on how the case was created. An alternative aproach would
|
||||||
|
* be to strip off any time stamp from the case name in the case node
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
|
String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory());
|
||||||
|
final List<String> nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS);
|
||||||
|
for (String manifestNodeName : nodeNames) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodeName);
|
||||||
|
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(nodeBytes);
|
||||||
|
if (caseName.equals(nodeData.getCaseName())) {
|
||||||
|
Path manifestFilePath = nodeData.getManifestFilePath();
|
||||||
|
if (manifestFilePath.toFile().exists()) {
|
||||||
|
manifestFilePaths.add(manifestFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (CoordinationServiceException | InvalidDataException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Error getting coordination service node data from %s", manifestNodeName), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,199 +566,217 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"# {0} - manifest file path", "DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}..."
|
"# {0} - manifest file path", "DeleteCaseTask.progress.lockingManifest=Locking manifest file {0}..."
|
||||||
})
|
})
|
||||||
private boolean acquireManifestFileLocks() throws CoordinationServiceException, InterruptedException {
|
private boolean acquireManifestFileLocks() throws IOException, CoordinationServiceException, InterruptedException {
|
||||||
/*
|
/*
|
||||||
* Get the "original" case name that from the case directory. This is
|
* When acquiring the locks, it is reasonable to block briefly, since
|
||||||
* necessary because the case display name can be changed and the case
|
* the auto ingest node (AIN) input directory scanning tasks do a lot of
|
||||||
* name may have a time stamp added to make it unique, depending on how
|
* short-term acquiring and releasing of the same locks. The assumption
|
||||||
* the case was created. An alternative aproach would be to strip the
|
* here is that the originator of this case deletion task is not asking
|
||||||
* time stamp from the case name in the case node data instead, but the
|
* for deletion of a case that has a job that an auto ingest node (AIN)
|
||||||
* code for that is already in the utility method called here.
|
* job execution task is working on and that
|
||||||
|
* MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long anyway, so
|
||||||
|
* waiting a bit should be fine.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory());
|
|
||||||
try {
|
|
||||||
boolean allLocksAcquired = true;
|
boolean allLocksAcquired = true;
|
||||||
// RJCTODO: Read in the list of manifests for the case instead of
|
|
||||||
// inspecting the nodes this way, once the recording of the
|
|
||||||
// manifests is in place.
|
|
||||||
final List<String> nodeNames = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS);
|
|
||||||
for (String manifestPath : nodeNames) {
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath);
|
|
||||||
if (nodeBytes == null || nodeBytes.length <= 0) {
|
|
||||||
logger.log(Level.WARNING, String.format("Empty coordination service node data found for %s", manifestPath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoIngestJobNodeData nodeData;
|
|
||||||
try {
|
try {
|
||||||
nodeData = new AutoIngestJobNodeData(nodeBytes);
|
for (Path manifestPath : manifestFilePaths) {
|
||||||
} catch (InvalidDataException ex) {
|
|
||||||
logger.log(Level.WARNING, String.format("Invalid coordination service node data found for %s", manifestPath), ex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caseName.equals(nodeData.getCaseName())) {
|
progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString()));
|
||||||
/*
|
|
||||||
* When acquiring manifest file locks, it is reasonable to
|
|
||||||
* block while acquiring this lock since the auto ingest
|
|
||||||
* node (AIN) input directory scanning tasks do a lot of
|
|
||||||
* short-term acquiring and releasing of manifest file
|
|
||||||
* locks. The assumption here is that the originator of this
|
|
||||||
* case deletion task is not asking for deletion of a case
|
|
||||||
* that has a job an auto ingest node (AIN) job processing
|
|
||||||
* task is working on and that
|
|
||||||
* MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long,
|
|
||||||
* anyway, so we can and should wait a bit.
|
|
||||||
*/
|
|
||||||
logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName()));
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath));
|
CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES);
|
||||||
CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath, MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES);
|
|
||||||
if (null != manifestLock) {
|
if (null != manifestLock) {
|
||||||
manifestFileLocks.add(manifestLock);
|
manifestFileLocks.add(manifestLock);
|
||||||
} else {
|
} else {
|
||||||
allLocksAcquired = false;
|
|
||||||
logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName()));
|
||||||
|
allLocksAcquired = false;
|
||||||
releaseManifestFileLocks();
|
releaseManifestFileLocks();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return allLocksAcquired;
|
|
||||||
|
|
||||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||||
releaseManifestFileLocks();
|
releaseManifestFileLocks();
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
return allLocksAcquired;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the auto ingest job input manifests for the case along with the
|
* Deletes the auto ingest job input manifests for the case along with the
|
||||||
* corresponding data sources.
|
* corresponding data sources.
|
||||||
*
|
*
|
||||||
* @throws IOException If there is an error opening the case
|
|
||||||
* manifests list file.
|
|
||||||
* @throws InterruptedException If the thread in which this task is running
|
* @throws InterruptedException If the thread in which this task is running
|
||||||
* is interrupted while blocked waiting for a
|
* is interrupted while blocked waiting for a
|
||||||
* coordination service operation to complete.
|
* coordination service operation to complete.
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
|
"DeleteCaseTask.progress.openingCaseDatabase=Opening the case database...",
|
||||||
|
"# {0} - manifest file path", "DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}...",
|
||||||
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}..."
|
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}..."
|
||||||
})
|
})
|
||||||
private void deleteAutoIngestInput() throws IOException, InterruptedException {
|
private void deleteAutoIngestInput() throws InterruptedException {
|
||||||
boolean allInputDeleted = true;
|
SleuthkitCase caseDb = null;
|
||||||
final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName());
|
try {
|
||||||
final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath);
|
progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase());
|
||||||
while (manifestsListFileScanner.hasNext()) {
|
logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName()));
|
||||||
|
caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
|
||||||
|
List<DataSource> dataSources = caseDb.getDataSources();
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
final String manifestFilePath = manifestsListFileScanner.next();
|
|
||||||
final File manifestFile = new File(manifestFilePath);
|
boolean allInputDeleted = true;
|
||||||
|
for (Path manifestFilePath : manifestFilePaths) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final File manifestFile = manifestFilePath.toFile();
|
||||||
if (manifestFile.exists()) {
|
if (manifestFile.exists()) {
|
||||||
// RJCTODO: Parse file, open case database, delete data sources
|
progress.progress(Bundle.DeleteCaseTask_progress_parsingManifest(manifestFilePath));
|
||||||
// before deleting manifest file
|
logger.log(Level.INFO, String.format("Parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||||
|
Manifest manifest = null;
|
||||||
|
for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) {
|
||||||
|
if (parser.fileIsManifest(manifestFilePath)) {
|
||||||
|
try {
|
||||||
|
manifest = parser.parse(manifestFilePath);
|
||||||
|
break;
|
||||||
|
} catch (ManifestFileParser.ManifestFileParserException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Error parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (manifest != null) {
|
||||||
|
if (deleteDataSources(manifest, dataSources)) {
|
||||||
|
/*
|
||||||
|
* Delete the manifest file, allowing a few retries.
|
||||||
|
* This is a way to resolve the race condition
|
||||||
|
* between this task and auto ingest node (AIN)
|
||||||
|
* input directory scanning tasks, which parse
|
||||||
|
* manifests (actually all files) before getting a
|
||||||
|
* coordination service lock, without resorting to a
|
||||||
|
* protocol using locking of the input directory.
|
||||||
|
*/
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath));
|
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath));
|
||||||
logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||||
if (manifestFile.delete()) {
|
int tries = 0;
|
||||||
|
boolean deleted = false;
|
||||||
|
while (!deleted && tries < MANIFEST_DELETE_TRIES) {
|
||||||
|
deleted = manifestFile.delete();
|
||||||
|
if (!deleted) {
|
||||||
|
++tries;
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deleted) {
|
||||||
|
/*
|
||||||
|
* Delete the input directory if it is empty.
|
||||||
|
*/
|
||||||
|
final Path inputDirectoryPath = manifestFilePath.getParent();
|
||||||
|
final File inputDirectory = inputDirectoryPath.toFile();
|
||||||
|
File[] files = inputDirectory.listFiles();
|
||||||
|
if (files == null || files.length == 0) {
|
||||||
|
if (!inputDirectory.delete()) {
|
||||||
|
logger.log(Level.WARNING, String.format("Failed to delete empty input directory %s for %s", inputDirectoryPath, caseNodeData.getDisplayName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||||
allInputDeleted = false;
|
allInputDeleted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (allInputDeleted) {
|
if (allInputDeleted) {
|
||||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES);
|
setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (TskCoreException | UserPreferencesException ex) {
|
||||||
|
logger.log(Level.INFO, String.format("Failed to open the case database for %s", caseNodeData.getDisplayName()), ex);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (caseDb != null) {
|
||||||
|
caseDb.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the case database, the text index, the case directory, and the
|
* Locates and deletes the data source files referenced by a manifest.
|
||||||
* case resources and auto ingest log coordination service lock nodes for
|
|
||||||
* the case.
|
|
||||||
*
|
*
|
||||||
* @return If true if all of the case output that was found was deleted,
|
* @param manifest A manifest.
|
||||||
* false otherwise.
|
* @param dataSources The data sources in the case as obtained from the case
|
||||||
|
* database.
|
||||||
*
|
*
|
||||||
* @throws InterruptedException If the thread in which this task is running
|
* @return True if all of the data source files werre deleted, false
|
||||||
* is interrupted while blocked waiting for a
|
* otherwise.
|
||||||
* coordination service operation to complete.
|
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({
|
private boolean deleteDataSources(Manifest manifest, List<DataSource> dataSources) {
|
||||||
"DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...",
|
/*
|
||||||
"DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources coordination service node...",
|
* There are two possibilities here. The data source may be an image,
|
||||||
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job coordination service node..."
|
* and if so, it may be split into multiple files. In this case, all of
|
||||||
})
|
* the files for the image need to be deleted. Otherwise, the data
|
||||||
private boolean deleteCaseOutput() throws InterruptedException {
|
* source is a single directory or file (a logical file, logical file
|
||||||
boolean errorsOccurred = false;
|
* set, report file, archive file, etc.). In this case, just the file
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile());
|
* referenced by the manifest will be deleted.
|
||||||
logger.log(Level.INFO, String.format("Locating metadata file for %s", caseNodeData.getDisplayName()));
|
*/
|
||||||
CaseMetadata caseMetadata = null;
|
boolean allFilesDeleted = true; // RJCTODO: add progress messages
|
||||||
final File caseDirectory = caseNodeData.getDirectory().toFile();
|
Set<Path> filesToDelete = new HashSet<>();
|
||||||
if (caseDirectory.exists()) {
|
final String dataSourceFileName = manifest.getDataSourceFileName();
|
||||||
final File[] filesInDirectory = caseDirectory.listFiles();
|
final String dataSourceDeviceId = manifest.getDeviceId();
|
||||||
if (filesInDirectory != null) {
|
for (DataSource dataSource : dataSources) {
|
||||||
for (File file : filesInDirectory) {
|
if (dataSource instanceof Image) {
|
||||||
if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
|
Image image = (Image) dataSource;
|
||||||
try {
|
if (image.getName().equals(dataSourceFileName) && image.getDeviceId().equals(dataSourceDeviceId)) {
|
||||||
caseMetadata = new CaseMetadata(Paths.get(file.getPath()));
|
String[] imageFilePaths = image.getPaths();
|
||||||
} catch (CaseMetadata.CaseMetadataException ex) {
|
for (String path : imageFilePaths) {
|
||||||
logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s", caseNodeData.getDisplayName()), ex);
|
Path imageFilePath = Paths.get(path);
|
||||||
|
filesToDelete.add(imageFilePath);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (filesToDelete.isEmpty()) {
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
final Path dataSourcePath = manifest.getDataSourcePath();
|
||||||
throw new InterruptedException();
|
filesToDelete.add(dataSourcePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caseMetadata != null) {
|
for (Path path : filesToDelete) {
|
||||||
logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName()));
|
File fileOrDir = path.toFile();
|
||||||
errorsOccurred = Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); // RJCTODO: CHeck for errors occurred?
|
if (fileOrDir.exists() && !FileUtil.deleteFileDir(fileOrDir)) {
|
||||||
} else {
|
allFilesDeleted = false;
|
||||||
logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s", caseNodeData.getDisplayName()));
|
logger.log(Level.INFO, String.format("Failed to delete data source file at %s for %s", path, caseNodeData.getDisplayName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
return allFilesDeleted;
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode());
|
/**
|
||||||
try {
|
* Examines a coordination service exception to try to determine if it is a
|
||||||
Case.deleteCaseResourcesLockNode(caseNodeData, progress);
|
* no node exception.
|
||||||
} catch (CoordinationServiceException ex) {
|
*
|
||||||
logger.log(Level.WARNING, String.format("Error deleting case resources coordiation service node for %s", caseNodeData.getDisplayName()), ex);
|
* @param ex A coordination service exception.
|
||||||
|
*
|
||||||
|
* @return True or false.
|
||||||
|
*/
|
||||||
|
private boolean isNoNodeException(CoordinationServiceException ex) {
|
||||||
|
boolean isNodeNodeEx = false;
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
if (cause != null) {
|
||||||
|
String causeMessage = cause.getMessage();
|
||||||
|
isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
|
||||||
}
|
}
|
||||||
|
return isNodeNodeEx;
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// RJCTODO: Check to see if getNodeData return null if the node does not exist;
|
|
||||||
// if so, make use of it
|
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode());
|
|
||||||
logger.log(Level.INFO, String.format("Deleting case auto ingest job log coordiation service node for %s", caseNodeData.getDisplayName()));
|
|
||||||
String logFilePath = CaseCoordinationServiceUtils.getCaseAutoIngestLogLockName(caseNodeData.getDirectory());
|
|
||||||
try {
|
|
||||||
coordinationService.deleteNode(CategoryNode.CASES, logFilePath);
|
|
||||||
} catch (CoordinationServiceException ex) {
|
|
||||||
logger.log(Level.WARNING, String.format("Error deleting case auto ingest job log coordiation service node for %s", caseNodeData.getDisplayName()), ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorsOccurred;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -569,7 +784,7 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
* task.
|
* task.
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive coordination service lock on the manifest file {0}..."
|
"# {0} - manifest file path", "DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}..."
|
||||||
})
|
})
|
||||||
private void releaseManifestFileLocks() {
|
private void releaseManifestFileLocks() {
|
||||||
for (Lock manifestFileLock : manifestFileLocks) {
|
for (Lock manifestFileLock : manifestFileLocks) {
|
||||||
@ -597,11 +812,13 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
* coordination service operation to complete.
|
* coordination service operation to complete.
|
||||||
*/
|
*/
|
||||||
@Messages({
|
@Messages({
|
||||||
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file coordination service node for {0}..."
|
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}..."
|
||||||
})
|
})
|
||||||
private boolean deleteManifestFileNodes() throws InterruptedException {
|
private void deleteManifestFileNodes() throws InterruptedException {
|
||||||
boolean allINodesDeleted = true;
|
boolean allINodesDeleted = true;
|
||||||
for (Lock manifestFileLock : manifestFileLocks) {
|
Iterator<Lock> iterator = manifestFileLocks.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Lock manifestFileLock = iterator.next();
|
||||||
String manifestFilePath = manifestFileLock.getNodePath();
|
String manifestFilePath = manifestFileLock.getNodePath();
|
||||||
try {
|
try {
|
||||||
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath));
|
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath));
|
||||||
@ -614,9 +831,11 @@ final class DeleteCaseTask implements Runnable {
|
|||||||
allINodesDeleted = false;
|
allINodesDeleted = false;
|
||||||
logger.log(Level.WARNING, String.format("Error deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
logger.log(Level.WARNING, String.format("Error deleting the manifest file coordination service node for %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||||
}
|
}
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
if (allINodesDeleted) {
|
||||||
|
setDeletedItemFlag(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES);
|
||||||
}
|
}
|
||||||
manifestFileLocks.clear();
|
|
||||||
return allINodesDeleted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user