mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +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}.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}.
|
||||
# {0} - case display name
|
||||
Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled.
|
||||
Case.exceptionMessage.emptyCaseDir=Must specify a case directory path.
|
||||
Case.exceptionMessage.emptyCaseName=Must specify a case name.
|
||||
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the 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.DataSourceNameChangedEvent;
|
||||
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.commonpropertiessearch.CommonAttributeSearchAction;
|
||||
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 CASE_ACTION_THREAD_NAME = "%s-case-action";
|
||||
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 AUTO_INGEST_LOG_FILE_NAME = "auto_ingest_log.txt";
|
||||
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
|
||||
private static final Logger logger = Logger.getLogger(Case.class.getName());
|
||||
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
|
||||
private static final Object caseActionSerializationLock = new Object();
|
||||
@ -1022,13 +1022,16 @@ public class Case {
|
||||
|
||||
try {
|
||||
deleteCaseResourcesLockNode(caseNodeData, progressIndicator);
|
||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||
errorsOccurred = true;
|
||||
} catch (CoordinationServiceException ex) {
|
||||
if (!isNoNodeException(ex)) {
|
||||
errorsOccurred = true;
|
||||
logger.log(Level.WARNING, String.format("Error deleting the case resources lock coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
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?
|
||||
|
||||
} 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);
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
|
||||
@ -1071,7 +1074,8 @@ public class Case {
|
||||
* during a wait.
|
||||
*/
|
||||
@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;
|
||||
try {
|
||||
deleteCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
|
||||
@ -1201,9 +1205,6 @@ public class Case {
|
||||
"Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
|
||||
})
|
||||
private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
// RJCTODO: Update FileUtil.deleteDir to use robocopy on Windows
|
||||
// when the path is >= 255 chars. Actually, deprecate this method and
|
||||
// replace it with one that throws instead of returning a boolean value.
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
|
||||
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()))) {
|
||||
@ -1273,7 +1274,7 @@ public class Case {
|
||||
@Beta
|
||||
public static void deleteCaseResourcesLockNode(CaseNodeData caseNodeData, ProgressIndicator progressIndicator) throws CoordinationServiceException, InterruptedException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesLockNode());
|
||||
String resourcesLockNodePath = caseNodeData.getDirectory().toString() + RESOURCES_LOCK_SUFFIX;//RJCTODO: Use utility
|
||||
String resourcesLockNodePath = CaseCoordinationServiceUtils.getCaseResourcesLockName(caseNodeData.getDirectory());
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
|
||||
}
|
||||
@ -1306,6 +1307,24 @@ public class Case {
|
||||
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
|
||||
* multi-user case.
|
||||
@ -1342,7 +1361,8 @@ public class Case {
|
||||
})
|
||||
private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
|
||||
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);
|
||||
return lock;
|
||||
} catch (InterruptedException ex) {
|
||||
|
@ -95,13 +95,13 @@ public final class CaseMetadata {
|
||||
private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS
|
||||
private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
|
||||
private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
|
||||
|
||||
|
||||
/*
|
||||
* Fields from schema version 5
|
||||
*/
|
||||
private static final String SCHEMA_VERSION_FIVE = "5.0";
|
||||
private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS
|
||||
|
||||
|
||||
/*
|
||||
* Unread fields, regenerated on save.
|
||||
*/
|
||||
@ -138,16 +138,39 @@ public final class CaseMetadata {
|
||||
public static DateFormat getDateFormat() {
|
||||
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
|
||||
* persisted to the case metadata file until writeFile or a setX method is
|
||||
* called.
|
||||
*
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
*/
|
||||
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
|
||||
this(caseType, caseDirectory, caseName, caseDetails, null);
|
||||
@ -158,11 +181,11 @@ public final class CaseMetadata {
|
||||
* persisted to the case metadata file until writeFile or a setX method is
|
||||
* called.
|
||||
*
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param originalMetadata The metadata object from the original case
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param originalMetadata The metadata object from the original case
|
||||
*/
|
||||
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) {
|
||||
metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
|
||||
@ -190,27 +213,6 @@ public final class CaseMetadata {
|
||||
this.metadataFilePath = metadataFilePath;
|
||||
readFromFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the case meta data file in the supplied directory. If the file does
|
||||
* not exist, null is returned.
|
||||
*
|
||||
* @param directoryPath Directory path to search
|
||||
* @return case meta data file path or null
|
||||
*/
|
||||
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.
|
||||
@ -460,7 +462,7 @@ public final class CaseMetadata {
|
||||
* Create the children of the case element.
|
||||
*/
|
||||
createCaseElements(doc, caseElement, this);
|
||||
|
||||
|
||||
/*
|
||||
* Add original case element
|
||||
*/
|
||||
@ -472,15 +474,15 @@ public final class CaseMetadata {
|
||||
originalCaseElement.appendChild(originalCaseDetailsElement);
|
||||
createCaseElements(doc, originalCaseDetailsElement, originalMetadata);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write the case element children for the given metadata object
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param caseElement The case element parent
|
||||
* @param metadataToWrite The CaseMetadata object to read from
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param caseElement The case element parent
|
||||
* @param metadataToWrite The CaseMetadata object to read from
|
||||
*/
|
||||
private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) {
|
||||
CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails;
|
||||
@ -572,8 +574,8 @@ public final class CaseMetadata {
|
||||
examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
|
||||
caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
|
||||
}
|
||||
|
||||
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail,
|
||||
|
||||
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail,
|
||||
caseNotes);
|
||||
this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
|
||||
if (null == this.caseType) {
|
||||
|
@ -289,8 +289,8 @@ public final class CaseNodeData {
|
||||
CASE_DB(2),
|
||||
CASE_DIR(4),
|
||||
DATA_SOURCES(8),
|
||||
MANIFEST_FILE_LOCK_NODES(16);
|
||||
|
||||
MANIFEST_FILE_NODES(16);
|
||||
|
||||
private final short value;
|
||||
|
||||
private DeletedFlags(int value) {
|
||||
|
@ -5,6 +5,8 @@ MultiUserCaseBrowserCustomizer.column.dataSourcesDeleteStatus=Data Sources Delet
|
||||
MultiUserCaseBrowserCustomizer.column.directory=Directory
|
||||
MultiUserCaseBrowserCustomizer.column.displayName=Name
|
||||
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
|
||||
MultiUserCaseNode.column.createTime=False
|
||||
MultiUserCaseNode.columnValue.true=True
|
||||
MultiUserCasesBrowserPanel.waitNode.message=Please Wait...
|
||||
|
@ -78,7 +78,7 @@ final class MultiUserCaseNode extends AbstractNode {
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, caseNodeData.getLastAccessDate()));
|
||||
break;
|
||||
case MANIFEST_FILE_ZNODES_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_LOCK_NODES)));
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.MANIFEST_FILE_NODES)));
|
||||
break;
|
||||
case DATA_SOURCES_DELETE_STATUS:
|
||||
sheetSet.put(new NodeProperty<>(propName, propName, propName, isDeleted(DeletedFlags.DATA_SOURCES)));
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -59,7 +59,19 @@ public final class ExecUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process terminator that can be used to kill a processes after it exceeds
|
||||
* A process terminator that can be used to kill a process spawned by a
|
||||
* thread that has been interrupted.
|
||||
*/
|
||||
public static class InterruptedThreadProcessTerminator implements ProcessTerminator {
|
||||
|
||||
@Override
|
||||
public boolean shouldTerminateProcess() {
|
||||
return Thread.currentThread().isInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A process terminator that can be used to kill a process after it exceeds
|
||||
* a maximum allowable run time.
|
||||
*/
|
||||
public static class TimedProcessTerminator implements ProcessTerminator {
|
||||
@ -212,9 +224,6 @@ public final class ExecUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EVERYTHING FOLLOWING THIS LINE IS DEPRECATED AND SLATED FOR REMOVAL
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(ExecUtil.class.getName());
|
||||
private Process proc = null;
|
||||
private ExecUtil.StreamToStringRedirect errorStringRedirect = null;
|
||||
|
@ -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)) {
|
||||
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());
|
||||
if (null != rawData && rawData.length > 0) {
|
||||
AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData);
|
||||
|
@ -8,9 +8,6 @@ AinStatusNode.status.shuttingdown=Shutting Down
|
||||
AinStatusNode.status.startingup=Starting Up
|
||||
AinStatusNode.status.title=Status
|
||||
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.cancelModuleAction.title=Cancel Module
|
||||
AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case.
|
||||
@ -169,43 +166,44 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard
|
||||
CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs
|
||||
CTL_CasesDashboardAction=Multi-User Cases Dashboard
|
||||
CTL_CasesDashboardTopComponent=Cases
|
||||
DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case.
|
||||
DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on "C:" drive
|
||||
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)
|
||||
DeleteCaseInputAction.menuItemText=Delete Input
|
||||
DeleteCaseInputAction.progressDisplayName=Delete Input
|
||||
DeleteCaseInputAction.taskName=input
|
||||
DeleteCaseInputAndOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-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.progressDisplayName=Delete Input and Output
|
||||
DeleteCaseInputAndOutputAction.taskName=input-and-output
|
||||
DeleteCaseOutputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-Manifest file znodes\n-Case database\n-Core.properties file\n-Case directory\n-Case znodes
|
||||
DeleteCaseOutputAction.menuItemText=Delete Output
|
||||
DeleteCaseOutputAction.progressDisplayName=Delete Output
|
||||
DeleteCaseOutputAction.taskName=output
|
||||
DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring an exclusive case directory lock...
|
||||
DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring an exclusive case name lock...
|
||||
DeleteCaseTask.progress.acquiringInputDirLocks=Acquiring exclusive input directory locks
|
||||
DeleteCaseTask.progress.acquiringManifestFileLocks=Acquiring exclusive manifest file locks...
|
||||
DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...
|
||||
DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...
|
||||
DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...
|
||||
DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...
|
||||
DeleteCaseTask.progress.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...
|
||||
# {0} - input directory name
|
||||
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}...
|
||||
DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.releasingManifestLock=Releasing the exclusive lock on manifest file {0}...
|
||||
DeleteCaseTask.progress.releasingManifestLocks=Releasing exclusive manifest file locks...
|
||||
DeleteCaseTask.progress.startMessage=Preparing for deletion...
|
||||
GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz)
|
||||
DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}...
|
||||
# {0} - manifest file path
|
||||
DeleteCaseTask.progress.deletingManifestFileNode=Deleting the manifest file znode for {0}...
|
||||
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
|
||||
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.
|
||||
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.closeButton.text=Close
|
||||
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:
|
||||
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
|
||||
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job
|
||||
|
@ -18,9 +18,11 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
@ -46,17 +48,27 @@ final class DeleteCaseInputAction extends DeleteCaseAction {
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseInputAction.menuItemText=Delete Input",
|
||||
"DeleteCaseInputAction.progressDisplayName=Delete Input",
|
||||
"DeleteCaseInputAction.taskName=input"
|
||||
"DeleteCaseInputAction.taskName=input"
|
||||
})
|
||||
DeleteCaseInputAction() {
|
||||
super(Bundle.DeleteCaseInputAction_menuItemText(), Bundle.DeleteCaseInputAction_progressDisplayName(), Bundle.DeleteCaseInputAction_taskName());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseInputAction.confirmationText=Are you sure you want to delete the following for the case(s):\n-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
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_INPUT, progress);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DeleteCaseInputAction clone() throws CloneNotSupportedException {
|
||||
super.clone();
|
||||
|
@ -18,8 +18,11 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
|
||||
@ -46,9 +49,19 @@ final class DeleteCaseInputAndOutputAction extends DeleteCaseAction {
|
||||
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
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) {
|
||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_OUTPUT, progress);
|
||||
return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_ALL, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,8 +18,10 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions;
|
||||
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());
|
||||
}
|
||||
|
||||
@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
|
||||
DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator 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.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.Lookup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
@ -39,62 +42,53 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.core.UserPreferencesException;
|
||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||
import org.sleuthkit.autopsy.progress.ProgressIndicator;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A task that deletes part or all of a given case. Note that all logging done
|
||||
* by this task is directed to the dedicated auto ingest dashboard log instead
|
||||
* of to the general application log.
|
||||
* A task that deletes part or all of a given case. Note that all logging is
|
||||
* directed to the dedicated auto ingest dashboard log instead of to the general
|
||||
* application log.
|
||||
*/
|
||||
// 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 {
|
||||
|
||||
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 final CaseNodeData caseNodeData;
|
||||
private final DeleteOptions deleteOption;
|
||||
private final ProgressIndicator progress;
|
||||
private final List<Path> manifestFilePaths;
|
||||
private final List<Lock> manifestFileLocks;
|
||||
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,
|
||||
* if any, while leaving the manifest file coordination service nodes
|
||||
* and the rest of the case intact. The use case is freeing auto ingest
|
||||
* input directory space while retaining the option to restore the data
|
||||
* while leaving the manifest file coordination service nodes and the
|
||||
* rest of the case intact. The use case is freeing auto ingest input
|
||||
* directory space while retaining the option to restore the data
|
||||
* sources, effectively restoring the case.
|
||||
*/
|
||||
DELETE_INPUT,
|
||||
/**
|
||||
* Delete the auto ingest job coordination service nodes, if any, and
|
||||
* the output for a case produced via auto ingest, while leaving the
|
||||
* auto ingest job input directories intact. The use case is auto ingest
|
||||
* Delete the manifest file coordination service nodes and the output
|
||||
* for a case, while leaving the auto ingest job manifests and
|
||||
* corresponding data sources intact. The use case is auto ingest
|
||||
* reprocessing of a case with a clean slate without having to restore
|
||||
* the input directories.
|
||||
* the manifests and data sources.
|
||||
*/
|
||||
DELETE_OUTPUT,
|
||||
/**
|
||||
@ -117,7 +111,8 @@ final class DeleteCaseTask implements Runnable {
|
||||
this.caseNodeData = caseNodeData;
|
||||
this.deleteOption = deleteOption;
|
||||
this.progress = progress;
|
||||
this.manifestFileLocks = new ArrayList<>();
|
||||
manifestFilePaths = new ArrayList<>();
|
||||
manifestFileLocks = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -127,9 +122,9 @@ final class DeleteCaseTask implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
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();
|
||||
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) {
|
||||
/*
|
||||
@ -139,6 +134,7 @@ final class DeleteCaseTask implements Runnable {
|
||||
* the task, so this ensures that any such errors get logged.
|
||||
*/
|
||||
logger.log(Level.SEVERE, String.format("Unexpected error deleting %s", caseNodeData.getDisplayName()), ex);
|
||||
throw ex;
|
||||
|
||||
} finally {
|
||||
progress.finish();
|
||||
@ -152,9 +148,13 @@ final class DeleteCaseTask implements Runnable {
|
||||
"DeleteCaseTask.progress.connectingToCoordSvc=Connecting to the coordination service...",
|
||||
"DeleteCaseTask.progress.acquiringCaseNameLock=Acquiring exclusive case name lock...",
|
||||
"DeleteCaseTask.progress.acquiringCaseDirLock=Acquiring exclusive case directory lock...",
|
||||
"DeleteCaseTask.progress.gettingManifestPaths=Getting manifest file paths...",
|
||||
"DeleteCaseTask.progress.acquiringManifestLocks=Acquiring exclusive manifest file locks...",
|
||||
"DeleteCaseTask.progress.deletingDirLockNode=Deleting case directory lock coordination service node...",
|
||||
"DeleteCaseTask.progress.deletingNameLockNode=Deleting case name lock coordination service node..."
|
||||
"DeleteCaseTask.progress.openingCaseMetadataFile=Opening case metadata file...",
|
||||
"DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources znode...",
|
||||
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest log znode...",
|
||||
"DeleteCaseTask.progress.deletingCaseDirCoordSvcNode=Deleting case directory znode...",
|
||||
"DeleteCaseTask.progress.deletingCaseNameCoordSvcNode=Deleting case name znode..."
|
||||
})
|
||||
private void deleteCase() {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
logger.log(Level.INFO, String.format("Connected to the coordination service for deletion of %s", caseNodeData.getDisplayName()));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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()));
|
||||
return;
|
||||
}
|
||||
logger.log(Level.INFO, String.format("Acquired an exclusive case name lock for %s", caseNodeData.getDisplayName()));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -205,7 +207,6 @@ final class DeleteCaseTask implements Runnable {
|
||||
* deleted open and prevents another node from trying to open the
|
||||
* 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());
|
||||
logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName()));
|
||||
String caseDirLockName = CaseCoordinationServiceUtils.getCaseDirectoryLockName(caseNodeData.getDirectory());
|
||||
@ -214,88 +215,183 @@ 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()));
|
||||
return;
|
||||
}
|
||||
logger.log(Level.INFO, String.format("Acquired an exclusive case directory lock for %s", caseNodeData.getDisplayName()));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Acquire exclusive locks for the auto ingest job manifest
|
||||
* files for the case, if any. Manifest file locks are acquired
|
||||
* by the auto ingest node (AIN) input directory scanning tasks
|
||||
* when they look for auto ingest jobs to enqueue, and by the
|
||||
* AIN job processing tasks when they execute a job. Acquiring
|
||||
* these locks here ensures that the scanning tasks and job
|
||||
* processing tasks cannot do anything with the auto ingest jobs
|
||||
* for a case during case deletion.
|
||||
*/
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks());
|
||||
logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_gettingManifestPaths());
|
||||
logger.log(Level.INFO, String.format("Getting manifest file paths for %s", caseNodeData.getDisplayName()));
|
||||
try {
|
||||
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()));
|
||||
return;
|
||||
}
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.WARNING, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex);
|
||||
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.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
* files for the case, if any. Manifest file locks are
|
||||
* acquired by the auto ingest node (AIN) input directory
|
||||
* scanning tasks when they look for auto ingest jobs to
|
||||
* enqueue, and by the AIN job execution tasks when they do
|
||||
* a job. Acquiring these locks here ensures that the
|
||||
* scanning tasks and job execution tasks cannot do anything
|
||||
* with the auto ingest jobs for a case during case
|
||||
* deletion.
|
||||
*/
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks());
|
||||
logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
||||
try {
|
||||
if (!acquireManifestFileLocks()) {
|
||||
logger.log(Level.INFO, String.format("Could not delete %s because at least one manifest file lock was already held by another host", caseNodeData.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
} catch (IOException | CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Could not delete %s because an error occurred acquiring the manifest file locks", caseNodeData.getDisplayName()), ex);
|
||||
return;
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||
return;
|
||||
}
|
||||
logger.log(Level.INFO, String.format("Acquired exclusive manifest file locks for %s", caseNodeData.getDisplayName()));
|
||||
|
||||
} else {
|
||||
logger.log(Level.INFO, String.format("No manifest file paths found for %s", caseNodeData.getDisplayName()));
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()));
|
||||
releaseManifestFileLocks();
|
||||
return;
|
||||
}
|
||||
|
||||
if (deleteOption == DeleteOptions.DELETE_INPUT || deleteOption == DeleteOptions.DELETE_ALL) {
|
||||
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 {
|
||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
||||
deleteAutoIngestInput();
|
||||
} catch (IOException ex) {
|
||||
// RJCTODO:
|
||||
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.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);
|
||||
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.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()));
|
||||
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 {
|
||||
success = deleteCaseOutput();
|
||||
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.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
||||
releaseManifestFileLocks();
|
||||
logger.log(Level.WARNING, String.format("Deletion of %s cancelled while incomplete", caseNodeData.getDisplayName()), ex);
|
||||
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 {
|
||||
releaseManifestFileLocks();
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.INFO, String.format("Deletion of %s cancelled", caseNodeData.getDisplayName()), ex);
|
||||
return;
|
||||
}
|
||||
releaseManifestFileLocks();
|
||||
|
||||
} 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);
|
||||
@ -303,7 +399,7 @@ final class DeleteCaseTask implements Runnable {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -314,14 +410,20 @@ final class DeleteCaseTask implements Runnable {
|
||||
* leave the node so that what was and was not deleted can be
|
||||
* inspected.
|
||||
*/
|
||||
if (success && (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL)) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingDirLockNode());
|
||||
if ((deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_ALL)
|
||||
&& 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 {
|
||||
Case.deleteCaseDirectoryLockNode(caseNodeData, progress);
|
||||
logger.log(Level.INFO, String.format("Deleted case directory znode for %s", caseNodeData.getDisplayName()));
|
||||
} 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -332,7 +434,7 @@ final class DeleteCaseTask implements Runnable {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -341,14 +443,109 @@ final class DeleteCaseTask implements Runnable {
|
||||
* service node for it can be deleted if the use case requires it.
|
||||
*/
|
||||
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 {
|
||||
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) {
|
||||
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) {
|
||||
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({
|
||||
"# {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
|
||||
* necessary because the case display name can be changed and the 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 the
|
||||
* time stamp from the case name in the case node data instead, but the
|
||||
* code for that is already in the utility method called here.
|
||||
* When acquiring the locks, it is reasonable to block briefly, since
|
||||
* the auto ingest node (AIN) input directory scanning tasks do a lot of
|
||||
* short-term acquiring and releasing of the same locks. The assumption
|
||||
* here is that the originator of this case deletion task is not asking
|
||||
* for deletion of a case that has a job that an auto ingest node (AIN)
|
||||
* job execution task is working on and that
|
||||
* MANIFEST_FILE_LOCKING_TIMEOUT_MINS is not very long anyway, so
|
||||
* waiting a bit should be fine.
|
||||
*
|
||||
*/
|
||||
String caseName = CaseCoordinationServiceUtils.getCaseNameLockName(caseNodeData.getDirectory());
|
||||
boolean allLocksAcquired = true;
|
||||
try {
|
||||
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) {
|
||||
for (Path manifestPath : manifestFilePaths) {
|
||||
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 {
|
||||
nodeData = new AutoIngestJobNodeData(nodeBytes);
|
||||
} catch (InvalidDataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Invalid coordination service node data found for %s", manifestPath), ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
if (caseName.equals(nodeData.getCaseName())) {
|
||||
/*
|
||||
* 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()));
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath));
|
||||
CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath, MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES);
|
||||
if (null != manifestLock) {
|
||||
manifestFileLocks.add(manifestLock);
|
||||
} 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()));
|
||||
releaseManifestFileLocks();
|
||||
break;
|
||||
}
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString()));
|
||||
logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName()));
|
||||
CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES);
|
||||
if (null != manifestLock) {
|
||||
manifestFileLocks.add(manifestLock);
|
||||
} else {
|
||||
logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName()));
|
||||
allLocksAcquired = false;
|
||||
releaseManifestFileLocks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return allLocksAcquired;
|
||||
|
||||
} catch (CoordinationServiceException | InterruptedException ex) {
|
||||
releaseManifestFileLocks();
|
||||
throw ex;
|
||||
}
|
||||
return allLocksAcquired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the auto ingest job input manifests for the case along with the
|
||||
* 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
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseTask.progress.openingCaseDatabase=Opening the case database...",
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}...",
|
||||
"# {0} - manifest file path", "DeleteCaseTask.progress.deletingManifest=Deleting manifest file {0}..."
|
||||
})
|
||||
private void deleteAutoIngestInput() throws IOException, InterruptedException {
|
||||
boolean allInputDeleted = true;
|
||||
final Path manifestsListFilePath = Paths.get(caseNodeData.getDirectory().toString(), AutoIngestManager.getCaseManifestsListFileName());
|
||||
final Scanner manifestsListFileScanner = new Scanner(manifestsListFilePath);
|
||||
while (manifestsListFileScanner.hasNext()) {
|
||||
private void deleteAutoIngestInput() throws InterruptedException {
|
||||
SleuthkitCase caseDb = null;
|
||||
try {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase());
|
||||
logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName()));
|
||||
caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
|
||||
List<DataSource> dataSources = caseDb.getDataSources();
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
final String manifestFilePath = manifestsListFileScanner.next();
|
||||
final File manifestFile = new File(manifestFilePath);
|
||||
if (manifestFile.exists()) {
|
||||
// RJCTODO: Parse file, open case database, delete data sources
|
||||
// before deleting manifest file
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
if (manifestFile.delete()) {
|
||||
logger.log(Level.WARNING, String.format("Failed to delete manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
allInputDeleted = false;
|
||||
|
||||
boolean allInputDeleted = true;
|
||||
for (Path manifestFilePath : manifestFilePaths) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
final File manifestFile = manifestFilePath.toFile();
|
||||
if (manifestFile.exists()) {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_parsingManifest(manifestFilePath));
|
||||
logger.log(Level.INFO, String.format("Parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
Manifest manifest = null;
|
||||
for (ManifestFileParser parser : Lookup.getDefault().lookupAll(ManifestFileParser.class)) {
|
||||
if (parser.fileIsManifest(manifestFilePath)) {
|
||||
try {
|
||||
manifest = parser.parse(manifestFilePath);
|
||||
break;
|
||||
} catch (ManifestFileParser.ManifestFileParserException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error parsing manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
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));
|
||||
logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
int tries = 0;
|
||||
boolean deleted = false;
|
||||
while (!deleted && tries < MANIFEST_DELETE_TRIES) {
|
||||
deleted = manifestFile.delete();
|
||||
if (!deleted) {
|
||||
++tries;
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
/*
|
||||
* 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()));
|
||||
allInputDeleted = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allInputDeleted) {
|
||||
setDeletedItemFlag(CaseNodeData.DeletedFlags.DATA_SOURCES);
|
||||
}
|
||||
|
||||
} catch (TskCoreException | UserPreferencesException ex) {
|
||||
logger.log(Level.INFO, String.format("Failed to open 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
|
||||
* case resources and auto ingest log coordination service lock nodes for
|
||||
* the case.
|
||||
* Locates and deletes the data source files referenced by a manifest.
|
||||
*
|
||||
* @return If true if all of the case output that was found was deleted,
|
||||
* false otherwise.
|
||||
* @param manifest A manifest.
|
||||
* @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
|
||||
* is interrupted while blocked waiting for a
|
||||
* coordination service operation to complete.
|
||||
* @return True if all of the data source files werre deleted, false
|
||||
* otherwise.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteCaseTask.progress.locatingCaseMetadataFile=Locating case metadata file...",
|
||||
"DeleteCaseTask.progress.deletingResourcesLockNode=Deleting case resources coordination service node...",
|
||||
"DeleteCaseTask.progress.deletingJobLogLockNode=Deleting case auto ingest job coordination service node..."
|
||||
})
|
||||
private boolean deleteCaseOutput() throws InterruptedException {
|
||||
boolean errorsOccurred = false;
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_locatingCaseMetadataFile());
|
||||
logger.log(Level.INFO, String.format("Locating metadata file for %s", caseNodeData.getDisplayName()));
|
||||
CaseMetadata caseMetadata = null;
|
||||
final File caseDirectory = caseNodeData.getDirectory().toFile();
|
||||
if (caseDirectory.exists()) {
|
||||
final File[] filesInDirectory = caseDirectory.listFiles();
|
||||
if (filesInDirectory != null) {
|
||||
for (File file : filesInDirectory) {
|
||||
if (file.getName().toLowerCase().endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
|
||||
try {
|
||||
caseMetadata = new CaseMetadata(Paths.get(file.getPath()));
|
||||
} catch (CaseMetadata.CaseMetadataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error getting opening case metadata file for %s", caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
break;
|
||||
private boolean deleteDataSources(Manifest manifest, List<DataSource> dataSources) {
|
||||
/*
|
||||
* There are two possibilities here. The data source may be an image,
|
||||
* and if so, it may be split into multiple files. In this case, all of
|
||||
* the files for the image need to be deleted. Otherwise, the data
|
||||
* source is a single directory or file (a logical file, logical file
|
||||
* set, report file, archive file, etc.). In this case, just the file
|
||||
* referenced by the manifest will be deleted.
|
||||
*/
|
||||
boolean allFilesDeleted = true; // RJCTODO: add progress messages
|
||||
Set<Path> filesToDelete = new HashSet<>();
|
||||
final String dataSourceFileName = manifest.getDataSourceFileName();
|
||||
final String dataSourceDeviceId = manifest.getDeviceId();
|
||||
for (DataSource dataSource : dataSources) {
|
||||
if (dataSource instanceof Image) {
|
||||
Image image = (Image) dataSource;
|
||||
if (image.getName().equals(dataSourceFileName) && image.getDeviceId().equals(dataSourceDeviceId)) {
|
||||
String[] imageFilePaths = image.getPaths();
|
||||
for (String path : imageFilePaths) {
|
||||
Path imageFilePath = Paths.get(path);
|
||||
filesToDelete.add(imageFilePath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filesToDelete.isEmpty()) {
|
||||
final Path dataSourcePath = manifest.getDataSourcePath();
|
||||
filesToDelete.add(dataSourcePath);
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
if (caseMetadata != null) {
|
||||
logger.log(Level.INFO, String.format("Deleting output for %s", caseNodeData.getDisplayName()));
|
||||
errorsOccurred = Case.deleteMultiUserCase(caseNodeData, caseMetadata, progress, logger); // RJCTODO: CHeck for errors occurred?
|
||||
} else {
|
||||
logger.log(Level.WARNING, String.format("Failed to locate metadata file for %s", caseNodeData.getDisplayName()));
|
||||
for (Path path : filesToDelete) {
|
||||
File fileOrDir = path.toFile();
|
||||
if (fileOrDir.exists() && !FileUtil.deleteFileDir(fileOrDir)) {
|
||||
allFilesDeleted = false;
|
||||
logger.log(Level.INFO, String.format("Failed to delete data source file at %s for %s", path, caseNodeData.getDisplayName()));
|
||||
}
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
return allFilesDeleted;
|
||||
}
|
||||
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode());
|
||||
try {
|
||||
Case.deleteCaseResourcesLockNode(caseNodeData, progress);
|
||||
} catch (CoordinationServiceException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error deleting case resources coordiation service node for %s", caseNodeData.getDisplayName()), ex);
|
||||
/**
|
||||
* 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 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);
|
||||
}
|
||||
|
||||
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;
|
||||
return isNodeNodeEx;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -569,7 +784,7 @@ final class DeleteCaseTask implements Runnable {
|
||||
* task.
|
||||
*/
|
||||
@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() {
|
||||
for (Lock manifestFileLock : manifestFileLocks) {
|
||||
@ -597,11 +812,13 @@ final class DeleteCaseTask implements Runnable {
|
||||
* coordination service operation to complete.
|
||||
*/
|
||||
@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;
|
||||
for (Lock manifestFileLock : manifestFileLocks) {
|
||||
Iterator<Lock> iterator = manifestFileLocks.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Lock manifestFileLock = iterator.next();
|
||||
String manifestFilePath = manifestFileLock.getNodePath();
|
||||
try {
|
||||
progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath));
|
||||
@ -614,9 +831,11 @@ final class DeleteCaseTask implements Runnable {
|
||||
allINodesDeleted = false;
|
||||
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