mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
integrate and update data src deletion
This commit is contained in:
parent
37fb384192
commit
8db2acfe16
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
package org.sleuthkit.autopsy.access;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
@ -26,7 +26,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
* Class for methods to check if access should be limited to a feature
|
||||
*
|
||||
*/
|
||||
final class AccessLimiterUtils {
|
||||
final public class AccessLimiterUtils {
|
||||
|
||||
private final static String MULTI_USER_ACCESS_FILE_NAME = "mualimit"; // NON-NLS
|
||||
private final static String MULTI_USER_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), MULTI_USER_ACCESS_FILE_NAME).toString();
|
||||
@ -36,7 +36,7 @@ final class AccessLimiterUtils {
|
||||
*
|
||||
* @return True if privileges should be restricted, false otherwise.
|
||||
*/
|
||||
static boolean limitMultiUserAccess() {
|
||||
public static boolean limitMultiUserAccess() {
|
||||
return new File(MULTI_USER_ACCESS_FILE_PATH).exists();
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ DeleteContentTagAction.deleteTag=Remove Selected Tag(s)
|
||||
DeleteContentTagAction.tagDelErr=Tag Deletion Error
|
||||
# {0} - tagName
|
||||
DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}.
|
||||
DeleteDataSourceAction.name.text=Delete Data Source
|
||||
DeleteFileBlackboardArtifactTagAction.deleteTag=Remove Result Tag
|
||||
# {0} - artifactID
|
||||
DeleteFileBlackboardArtifactTagAction.deleteTag.alert=Unable to untag artifact {0}.
|
||||
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.Lookup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
|
||||
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* An Action that allows a user to delete a data source.
|
||||
*/
|
||||
public final class DeleteDataSourceAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(DeleteDataSourceAction.class.getName());
|
||||
private long dataSourceID;
|
||||
|
||||
/**
|
||||
* Constructs an Action that allows a user to delete a data source.
|
||||
*
|
||||
* @param dataSourceID The object ID of the data source to be deleted.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteDataSourceAction.name.text=Delete Data Source"
|
||||
})
|
||||
public DeleteDataSourceAction(Long dataSourceID) {
|
||||
super(Bundle.DeleteDataSourceAction_name_text());
|
||||
this.dataSourceID = dataSourceID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
// try {
|
||||
// Case.getCurrentCaseThrows().getSleuthkitCase().deleteDataSource(dataSourceID);
|
||||
// KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class);
|
||||
// kwsService.deleteDataSource(dataSourceID);
|
||||
// Case.getCurrentCaseThrows().notifyDataSourceDeleted(dataSourceID);
|
||||
// } catch (NoCurrentCaseException | TskCoreException | KeywordSearchServiceException e) {
|
||||
// logger.log(Level.SEVERE, String.format("Error Deleting data source (obj_id=%d)", dataSourceID), e);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteDataSourceAction clone() throws CloneNotSupportedException {
|
||||
DeleteDataSourceAction clonedObject = ((DeleteDataSourceAction) super.clone());
|
||||
clonedObject.setDataSourceID(this.dataSourceID);
|
||||
return clonedObject;
|
||||
}
|
||||
|
||||
private void setDataSourceID(long dataSourceID) {
|
||||
this.dataSourceID = dataSourceID;
|
||||
}
|
||||
|
||||
}
|
@ -10,7 +10,6 @@ Case.deleteCaseConfirmationDialog.title=Delete Current Case?
|
||||
Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}
|
||||
Case.deleteCaseFailureMessageBox.title=Failed to Delete Case
|
||||
Case.DeletingDataSourceFromCase=Deleting the Data Source from the case.
|
||||
Case.ErrorDeletingDataSource.name.text=Error Deleting Data Source
|
||||
Case.exceptionMessage.cancelledByUser=Cancelled by user.
|
||||
Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.
|
||||
Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.
|
||||
@ -38,6 +37,8 @@ Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination s
|
||||
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.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.errorDeletingDataSource=An error occurred while deleting the data source:\n{0}.
|
||||
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.execExceptionWrapperMessage={0}
|
||||
@ -54,14 +55,11 @@ Case.exceptionMessage.metadataUpdateError=Failed to update case metadata
|
||||
Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.
|
||||
Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User.
|
||||
Case.progressIndicatorCancelButton.label=Cancel
|
||||
Case.progressIndicatorStatus_closingCase=Closing Case to Deleting Data Source.
|
||||
Case.progressIndicatorStatus_deletingDataSource=Deleting Data Source.
|
||||
Case.progressIndicatorStatus_openingCase=Opening Case to Deleting Data Source.
|
||||
Case.progressIndicatorTitle.closingCase=Closing Case
|
||||
Case.progressIndicatorTitle.creatingCase=Creating Case
|
||||
Case.progressIndicatorTitle.deletingCase=Deleting Case
|
||||
Case.progressIndicatorTitle.deletingDataSource=Deleting Data Source
|
||||
Case.progressIndicatorTitle.openingCase=Opening Case
|
||||
Case.progressIndicatorTitle_deletingDataSource=Deleting Data Source from the case.
|
||||
Case.progressMessage.cancelling=Cancelling...
|
||||
Case.progressMessage.clearingTempDirectory=Clearing case temp directory...
|
||||
Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...
|
||||
@ -124,6 +122,10 @@ CTL_CaseDetailsAction=Case Details
|
||||
CTL_CaseDeleteAction=Delete Case
|
||||
CTL_CaseOpenAction=Open Case
|
||||
CTL_UnpackagePortableCaseAction=Unpack and Open Portable Case
|
||||
DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to delete the selected data source from the case?
|
||||
DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to reopen the case.
|
||||
DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while deleting the data source.\nPlease see the application log for details.
|
||||
DeleteDataSourceAction.name.text=Delete Data Source
|
||||
EditOptionalCasePropertiesPanel.cancelButton.text=Cancel
|
||||
EditOptionalCasePropertiesPanel.saveButton.text=Save
|
||||
GeneralFilter.encaseImageDesc.text=Encase Images (*.e01)
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import org.sleuthkit.autopsy.access.AccessLimiterUtils;
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
@ -52,6 +53,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@ -741,9 +743,9 @@ public class Case {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected data source.
|
||||
* Deletes a data source from the current cases.
|
||||
*
|
||||
* @param dataSourceId id of the data source to delete.
|
||||
* @param dataSourceObjectID The object ID of the data source to delete.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem deleting the case. The
|
||||
* exception will have a user-friendly message
|
||||
@ -751,64 +753,20 @@ public class Case {
|
||||
* exception.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressIndicatorTitle_deletingDataSource=Deleting Data Source from the case.",
|
||||
"Case.progressIndicatorStatus_closingCase=Closing Case to Deleting Data Source.",
|
||||
"Case.progressIndicatorStatus_openingCase=Opening Case to Deleting Data Source.",
|
||||
"Case.progressIndicatorStatus_deletingDataSource=Deleting Data Source.",})
|
||||
public static void deleteDataSourceFromCurrentCase(Long dataSourceId) throws CaseActionException {
|
||||
"Case.progressIndicatorTitle.deletingDataSource=Deleting Data Source"
|
||||
})
|
||||
public static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
|
||||
synchronized (caseActionSerializationLock) {
|
||||
if (null == currentCase) {
|
||||
return;
|
||||
}
|
||||
CaseMetadata newMetadata = null;
|
||||
CaseMetadata metadata = currentCase.getMetadata();
|
||||
String caseDir = metadata.getFilePath().toString();
|
||||
try {
|
||||
newMetadata = new CaseMetadata(Paths.get(caseDir));
|
||||
} catch (CaseMetadataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error Getting Case Dir %s", caseDir), ex);
|
||||
}
|
||||
ProgressIndicator progressIndicator;
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingDataSource());
|
||||
} else {
|
||||
progressIndicator = new LoggingProgressIndicator();
|
||||
}
|
||||
Case theCase = currentCase;
|
||||
closeCurrentCase();
|
||||
progressIndicator.switchToIndeterminate(Bundle.Case_progressIndicatorStatus_openingCase());
|
||||
progressIndicator.start(Bundle.Case_progressIndicatorStatus_deletingDataSource());
|
||||
deleteDataSource(dataSourceId, progressIndicator, metadata);
|
||||
progressIndicator.finish();
|
||||
openAsCurrentCase(new Case(newMetadata), false);
|
||||
theCase.doCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), null, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
|
||||
openAsCurrentCase(theCase, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a data source from the current case.
|
||||
*
|
||||
* @param dataSourceId id of the data source to delete.
|
||||
* @param progressIndicator
|
||||
*/
|
||||
@Messages({
|
||||
"Case.DeletingDataSourceFromCase=Deleting the Data Source from the case.",
|
||||
"Case.ErrorDeletingDataSource.name.text=Error Deleting Data Source"
|
||||
})
|
||||
static void deleteDataSource(Long dataSourceId, ProgressIndicator progressIndicator, CaseMetadata metadata) throws CaseActionException {
|
||||
// get case actions lock
|
||||
closeCurrentCase();
|
||||
// If multi-user case, get exclusive lock on case, else continue w/o lock
|
||||
Case theCase = new Case(metadata);
|
||||
theCase.openCaseDataBase(progressIndicator);
|
||||
theCase.openAppServiceCaseResources(progressIndicator);
|
||||
try {
|
||||
SleuthkitCaseAdmin.deleteDataSource(null, dataSourceId);
|
||||
} catch (TskCoreException ex) {
|
||||
}
|
||||
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceId));
|
||||
theCase.close(progressIndicator);
|
||||
openAsCurrentCase(new Case(metadata), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a case. The case to be deleted must not be the "current case."
|
||||
* Deleting the current case must be done by calling Case.deleteCurrentCase.
|
||||
@ -870,6 +828,8 @@ public class Case {
|
||||
* @throws CaseActionCancelledException If creating the case is cancelled.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressIndicatorTitle.creatingCase=Creating Case",
|
||||
"Case.progressIndicatorTitle.openingCase=Opening Case",
|
||||
"Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
|
||||
})
|
||||
private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
|
||||
@ -887,7 +847,16 @@ public class Case {
|
||||
}
|
||||
try {
|
||||
logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
|
||||
newCurrentCase.open(isNewCase);
|
||||
String progressIndicatorTitle;
|
||||
CaseAction<ProgressIndicator, Object, Void> caseAction;
|
||||
if (isNewCase) {
|
||||
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
|
||||
caseAction = newCurrentCase::createCase;
|
||||
} else {
|
||||
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
|
||||
caseAction = newCurrentCase::openCase;
|
||||
}
|
||||
newCurrentCase.doCaseAction(progressIndicatorTitle, caseAction, CaseLockType.SHARED, true, null);
|
||||
currentCase = newCurrentCase;
|
||||
logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
@ -1553,18 +1522,6 @@ public class Case {
|
||||
eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies case event subscribers that a data source has been deleted from
|
||||
* the case database.
|
||||
*
|
||||
* This should not be called from the event dispatch thread (EDT)
|
||||
*
|
||||
* @param dataSourceId The object ID of the data source that was deleted.
|
||||
*/
|
||||
public void notifyDataSourceDeleted(Long dataSourceId) {
|
||||
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies case event subscribers that a content tag has been added.
|
||||
*
|
||||
@ -1795,23 +1752,30 @@ public class Case {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this case by creating a task running in the same non-UI thread that
|
||||
* will be used to close the case. If the case is a single-user case, this
|
||||
* supports cancelling creation of the case by cancelling the task. If the
|
||||
* case is a multi-user case, this ensures ensures that case directory lock
|
||||
* held as long as the case is open is released in the same thread in which
|
||||
* it was acquired, as is required by the coordination service.
|
||||
* Performs a case action by creating a task running in the same non-UI
|
||||
* thread that will be used to close the case. For both single-user and
|
||||
* mulit-user cases, this supports cancelling the case action by cancelling
|
||||
* the task. If the case is a multi-user case, this also ensures that the
|
||||
* case lock is released in the same thread in which it was acquired, which
|
||||
* is required by the coordination service.
|
||||
*
|
||||
* @param isNewCase True for a new case, false otherwise.
|
||||
* @param progressIndicatorTitle A title for the progress indicator for the
|
||||
* case action.
|
||||
* @param caseAction The case action method.
|
||||
* @param caseLockType The type of case lock required for the case
|
||||
* action.
|
||||
* @param allowCancellation Whether or not to allow the action to be
|
||||
* cancelled.
|
||||
* @param additionalParams An Object that holds any additional
|
||||
* parameters for a case action. For this
|
||||
* action, this is null.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem creating the case. The
|
||||
* exception will have a user-friendly message
|
||||
* and may be a wrapper for a lower-level
|
||||
* exception.
|
||||
* @throws CaseActionException If there is a problem completing the action.
|
||||
* The exception will have a user-friendly
|
||||
* message and may be a wrapper for a
|
||||
* lower-level exception.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressIndicatorTitle.creatingCase=Creating Case",
|
||||
"Case.progressIndicatorTitle.openingCase=Opening Case",
|
||||
"Case.progressIndicatorCancelButton.label=Cancel",
|
||||
"Case.progressMessage.preparing=Preparing...",
|
||||
"Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
|
||||
@ -1819,62 +1783,66 @@ public class Case {
|
||||
"Case.exceptionMessage.cancelledByUser=Cancelled by user.",
|
||||
"# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
|
||||
})
|
||||
private void open(boolean isNewCase) throws CaseActionException {
|
||||
private void doCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
|
||||
/*
|
||||
* Create and start either a GUI progress indicator with a Cancel button
|
||||
* or a logging progress indicator.
|
||||
* Create and start either a GUI progress indicator or a logging
|
||||
* progress indicator.
|
||||
*/
|
||||
CancelButtonListener cancelButtonListener = null;
|
||||
ProgressIndicator progressIndicator;
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
|
||||
String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
|
||||
progressIndicator = new ModalDialogProgressIndicator(
|
||||
mainFrame,
|
||||
progressIndicatorTitle,
|
||||
new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
|
||||
Bundle.Case_progressIndicatorCancelButton_label(),
|
||||
cancelButtonListener);
|
||||
if (allowCancellation) {
|
||||
cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
|
||||
progressIndicator = new ModalDialogProgressIndicator(
|
||||
mainFrame,
|
||||
progressIndicatorTitle,
|
||||
new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
|
||||
Bundle.Case_progressIndicatorCancelButton_label(),
|
||||
cancelButtonListener);
|
||||
} else {
|
||||
progressIndicator = new ModalDialogProgressIndicator(
|
||||
mainFrame,
|
||||
progressIndicatorTitle);
|
||||
}
|
||||
} else {
|
||||
progressIndicator = new LoggingProgressIndicator();
|
||||
}
|
||||
progressIndicator.start(Bundle.Case_progressMessage_preparing());
|
||||
|
||||
/*
|
||||
* Creating/opening a case is always done by creating a task running in
|
||||
* the same non-UI thread that will be used to close the case, so a
|
||||
* A case action is always done by creating a task running in the same
|
||||
* non-UI thread that will be used to close the case, so a
|
||||
* single-threaded executor service is created here and saved as case
|
||||
* state (must be volatile for cancellation to work).
|
||||
*
|
||||
* --- If the case is a single-user case, this supports cancelling
|
||||
* opening of the case by cancelling the task.
|
||||
* --- If the case is a single-user case, this supports cancelling the
|
||||
* case action by cancelling the task.
|
||||
*
|
||||
* --- If the case is a multi-user case, this still supports
|
||||
* cancellation, but it also makes it possible for the shared case
|
||||
* directory lock held as long as the case is open to be released in the
|
||||
* same thread in which it was acquired, as is required by the
|
||||
* coordination service.
|
||||
* cancellation, but it also makes it possible for the case lock held as
|
||||
* long as the case is open to be released in the same thread in which
|
||||
* it was acquired, as is required by the coordination service.
|
||||
*/
|
||||
TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
|
||||
caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
|
||||
Future<Void> future = caseLockingExecutor.submit(() -> {
|
||||
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
|
||||
open(isNewCase, progressIndicator);
|
||||
caseAction.execute(progressIndicator, additionalParams);
|
||||
} else {
|
||||
/*
|
||||
* First, acquire a shared case directory lock that will be held
|
||||
* as long as this node has this case open. This will prevent
|
||||
* deletion of the case by another node. Next, acquire an
|
||||
* exclusive case resources lock to ensure only one node at a
|
||||
* time can create/open/upgrade/close the case resources.
|
||||
* First, acquire a case lock that will be held as long as this
|
||||
* node has this case open. This will prevent deletion of the
|
||||
* case by another node. Next, acquire an exclusive case
|
||||
* resources lock to ensure only one node at a time can
|
||||
* create/open/upgrade/close the case resources.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
|
||||
acquireSharedCaseDirLock(metadata.getCaseDirectory());
|
||||
acquireCaseLock(caseLockType, metadata.getCaseDirectory());
|
||||
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) {
|
||||
if (null == resourcesLock) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
|
||||
}
|
||||
open(isNewCase, progressIndicator);
|
||||
caseAction.execute(progressIndicator, additionalParams);
|
||||
} catch (CaseActionException ex) {
|
||||
releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
|
||||
throw ex;
|
||||
@ -1887,18 +1855,17 @@ public class Case {
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for the case creation/opening task to finish.
|
||||
* Wait for the case action task to finish.
|
||||
*/
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException discarded) {
|
||||
/*
|
||||
* The thread this method is running in has been interrupted. Cancel
|
||||
* the create/open task, wait for it to finish, and shut down the
|
||||
* the case action task, wait for it to finish, and shut down the
|
||||
* executor. This can be done safely because if the task is
|
||||
* completed with a cancellation condition, the case will have been
|
||||
* closed and the case directory lock released will have been
|
||||
* released.
|
||||
* closed and the case lock will have been released.
|
||||
*/
|
||||
if (null != cancelButtonListener) {
|
||||
cancelButtonListener.actionPerformed(null);
|
||||
@ -1908,21 +1875,20 @@ public class Case {
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
} catch (CancellationException discarded) {
|
||||
/*
|
||||
* The create/open task has been cancelled. Wait for it to finish,
|
||||
* The case action task has been cancelled. Wait for it to finish,
|
||||
* and shut down the executor. This can be done safely because if
|
||||
* the task is completed with a cancellation condition, the case
|
||||
* will have been closed and the case directory lock released will
|
||||
* have been released.
|
||||
* will have been closed and the case lock will have been released.
|
||||
*/
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
|
||||
} catch (ExecutionException ex) {
|
||||
/*
|
||||
* The create/open task has thrown an exception. Wait for it to
|
||||
* The case action task has thrown an exception. Wait for it to
|
||||
* finish, and shut down the executor. This can be done safely
|
||||
* because if the task is completed with an execution condition, the
|
||||
* case will have been closed and the case directory lock released
|
||||
* will have been released.
|
||||
* case will have been closed and the case lock will have been
|
||||
* released.
|
||||
*/
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
|
||||
@ -1932,48 +1898,41 @@ public class Case {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the case database and services for this case.
|
||||
* A case action (interface CaseAction<T, V, R>) that creates the case
|
||||
* directory and case database and opens the application services for this
|
||||
* case.
|
||||
*
|
||||
* @param isNewCase True for a new case, false otherwise.
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param additionalParams An Object that holds any additional parameters
|
||||
* for a case action. For this action, this is
|
||||
* null.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem creating the case. The
|
||||
* exception will have a user-friendly message
|
||||
* and may be a wrapper for a lower-level
|
||||
* exception.
|
||||
* @throws CaseActionException If there is a problem completing the action.
|
||||
* The exception will have a user-friendly
|
||||
* message and may be a wrapper for a
|
||||
* lower-level exception.
|
||||
*/
|
||||
private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
private Void createCase(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
|
||||
assert (additionalParams == null);
|
||||
try {
|
||||
checkForUserCancellation();
|
||||
createCaseDirectoryIfDoesNotExist(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
switchLoggingToCaseLogsDirectory(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
if (isNewCase) {
|
||||
saveCaseMetadataToFile(progressIndicator);
|
||||
}
|
||||
saveCaseMetadataToFile(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
if (isNewCase) {
|
||||
createCaseNodeData(progressIndicator);
|
||||
} else {
|
||||
updateCaseNodeData(progressIndicator);
|
||||
}
|
||||
createCaseNodeData(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
if (!isNewCase) {
|
||||
deleteTempfilesFromCaseDirectory(progressIndicator);
|
||||
}
|
||||
checkForUserCancellation();
|
||||
if (isNewCase) {
|
||||
createCaseDatabase(progressIndicator);
|
||||
} else {
|
||||
openCaseDataBase(progressIndicator);
|
||||
}
|
||||
createCaseDatabase(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openCaseLevelServices(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openAppServiceCaseResources(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openCommunicationChannels(progressIndicator);
|
||||
return null;
|
||||
|
||||
} catch (CaseActionException ex) {
|
||||
/*
|
||||
@ -1991,6 +1950,96 @@ public class Case {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A case action (interface CaseAction<T, V, R>) that opens the case
|
||||
* database and services for this case.
|
||||
*
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param additionalParams An Object that holds any additional parameters
|
||||
* for a case action. For this action, this is
|
||||
* null.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem completing the action.
|
||||
* The exception will have a user-friendly
|
||||
* message and may be a wrapper for a
|
||||
* lower-level exception.
|
||||
*/
|
||||
private Void openCase(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
|
||||
assert (additionalParams == null);
|
||||
try {
|
||||
checkForUserCancellation();
|
||||
switchLoggingToCaseLogsDirectory(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
updateCaseNodeData(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
deleteTempfilesFromCaseDirectory(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openCaseDataBase(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openCaseLevelServices(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openAppServiceCaseResources(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
openCommunicationChannels(progressIndicator);
|
||||
return null;
|
||||
|
||||
} catch (CaseActionException ex) {
|
||||
/*
|
||||
* Cancellation or failure. Clean up by calling the close method.
|
||||
* The sleep is a little hack to clear the interrupted flag for this
|
||||
* thread if this is a cancellation scenario, so that the clean up
|
||||
* can run to completion in the current thread.
|
||||
*/
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException discarded) {
|
||||
}
|
||||
close(progressIndicator);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A case action (interface CaseAction<T, V, R>) for a closed case that
|
||||
* opens the case database and application services for this case, deletes a
|
||||
* data source from the case, and publishes an application event indicasting
|
||||
* the data source has been deleted.
|
||||
*
|
||||
* Note that this case action does not support cancellation.
|
||||
*
|
||||
* @param progressIndicator A progress indicator.
|
||||
* @param additionalParams An Object that holds any additional parameters
|
||||
* for a case action. For this action, this the
|
||||
* object ID of the data source to be deleted.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem completing the action.
|
||||
* The exception will have a user-friendly
|
||||
* message and may be a wrapper for a
|
||||
* lower-level exception.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.DeletingDataSourceFromCase=Deleting the Data Source from the case.",
|
||||
"# {0} - exception message", "Case.exceptionMessage.errorDeletingDataSource=An error occurred while deleting the data source:\n{0}."
|
||||
})
|
||||
Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
|
||||
assert (additionalParams instanceof Long);
|
||||
openCaseDataBase(progressIndicator);
|
||||
openAppServiceCaseResources(progressIndicator);
|
||||
Long dataSourceObjectID = (Long) additionalParams;
|
||||
try {
|
||||
SleuthkitCaseAdmin.deleteDataSource(this.caseDb, dataSourceObjectID);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSource(ex.getMessage()), ex);
|
||||
}
|
||||
try {
|
||||
this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
|
||||
} catch (KeywordSearchServiceException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSource(ex.getMessage()), ex);
|
||||
}
|
||||
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty portable case from the current case
|
||||
*
|
||||
@ -2475,7 +2524,7 @@ public class Case {
|
||||
/*
|
||||
* The wait has been interrupted by interrupting the thread running
|
||||
* this method. Not allowing cancellation of case closing, so ignore
|
||||
* the interrupt. Likewsie, cancellation of the case closing task is
|
||||
* the interrupt. Likewise, cancellation of the case closing task is
|
||||
* not supported.
|
||||
*/
|
||||
} catch (ExecutionException ex) {
|
||||
@ -2595,9 +2644,13 @@ public class Case {
|
||||
* cannot be acquired.
|
||||
*/
|
||||
@Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory"})
|
||||
private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
|
||||
private void acquireCaseLock(CaseLockType lockType, String caseDir) throws CaseActionException {
|
||||
try {
|
||||
caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
||||
boolean flag = true;
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
caseDirLock = lockType == CaseLockType.SHARED
|
||||
? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS)
|
||||
: coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
||||
if (null == caseDirLock) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
|
||||
}
|
||||
@ -2994,6 +3047,15 @@ public class Case {
|
||||
}
|
||||
}
|
||||
|
||||
private interface CaseAction<T, V, R> {
|
||||
|
||||
R execute(T t, V v) throws CaseActionException;
|
||||
}
|
||||
|
||||
private enum CaseLockType {
|
||||
SHARED, EXCLUSIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A case operation Cancel button listener for use with a
|
||||
* ModalDialogProgressIndicator when running with a GUI.
|
||||
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
|
||||
/**
|
||||
* An Action that allows a user to delete a data source from the current case.
|
||||
*/
|
||||
public final class DeleteDataSourceAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(DeleteDataSourceAction.class.getName());
|
||||
private long dataSourceObjectID;
|
||||
private Path caseMetadataFilePath;
|
||||
|
||||
/**
|
||||
* Constructs an Action that allows a user to delete a data source.
|
||||
*
|
||||
* @param dataSourceObjectID The object ID of the data source to be deleted.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteDataSourceAction.name.text=Delete Data Source"
|
||||
})
|
||||
public DeleteDataSourceAction(Long dataSourceObjectID) {
|
||||
super(Bundle.DeleteDataSourceAction_name_text());
|
||||
this.dataSourceObjectID = dataSourceObjectID;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to delete the selected data source from the case?",
|
||||
"DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while deleting the data source.\nPlease see the application log for details.",
|
||||
"DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to reopen the case.",})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteDataSourceAction_confirmationDialog_message())) {
|
||||
new SwingWorker<Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath();
|
||||
/*
|
||||
* Note that the case is closed and re-opened by this case
|
||||
* action.
|
||||
*/
|
||||
Case.deleteDataSourceFromCurrentCase(dataSourceObjectID);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error deleting data source (obj_id=%d)", dataSourceObjectID), ex);
|
||||
MessageNotifyUtil.Message.show(Bundle.DeleteDataSourceAction_exceptionMessage_dataSourceDeletionError(), MessageNotifyUtil.MessageType.ERROR);
|
||||
if (!Case.isCaseOpen()) {
|
||||
try {
|
||||
Case.openAsCurrentCase(caseMetadataFilePath.toString());
|
||||
} catch (CaseActionException ex2) {
|
||||
logger.log(Level.SEVERE, "Failed to reopen the case after data source deletion error", ex2);
|
||||
MessageNotifyUtil.Message.show(Bundle.DeleteDataSourceAction_exceptionMessage_couldNotReopenCase(), MessageNotifyUtil.MessageType.ERROR);
|
||||
StartupWindowProvider.getInstance().open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteDataSourceAction clone() throws CloneNotSupportedException {
|
||||
DeleteDataSourceAction clonedObject = ((DeleteDataSourceAction) super.clone());
|
||||
clonedObject.setDataSourceID(this.dataSourceObjectID);
|
||||
return clonedObject;
|
||||
}
|
||||
|
||||
private void setDataSourceID(long dataSourceID) {
|
||||
this.dataSourceObjectID = dataSourceID;
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import org.sleuthkit.autopsy.access.AccessLimiterUtils;
|
||||
import java.awt.Component;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Copyright 2012-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -20,8 +20,6 @@ package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
@ -35,14 +33,14 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.actions.DeleteDataSourceAction;
|
||||
import org.sleuthkit.autopsy.access.AccessLimiterUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
|
||||
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
|
||||
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
@ -66,10 +64,6 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ImageNode.class.getName());
|
||||
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED);
|
||||
private final static String ADMIN_ACCESS_FILE_NAME = "admin"; // NON-NLS
|
||||
private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_ACCESS_FILE_NAME).toString();
|
||||
private final static String ADMIN_EXT_ACCESS_FILE_NAME = "adminext"; // NON-NLS
|
||||
private final static String ADMIN_EXT_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_EXT_ACCESS_FILE_NAME).toString();
|
||||
|
||||
/**
|
||||
* Helper so that the display name and the name used in building the path
|
||||
@ -128,7 +122,7 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
actionsList.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
|
||||
actionsList.add(new NewWindowViewAction(
|
||||
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
|
||||
if (checkSchemaVersion() && checkMuAdmin()) {
|
||||
if (canAddDeleteDataSourceAction()) {
|
||||
actionsList.add(new DeleteDataSourceAction(content.getId()));
|
||||
}
|
||||
return actionsList.toArray(new Action[0]);
|
||||
@ -218,31 +212,19 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
private Boolean checkSchemaVersion() {
|
||||
try {
|
||||
CaseDbSchemaVersionNumber creationVersion = Case.getCurrentCaseThrows().getSleuthkitCase().getDBSchemaCreationVersion();
|
||||
|
||||
if ((creationVersion.getMajor() == 8 && creationVersion.getMinor() >= 3) || creationVersion.getMajor() > 8) {
|
||||
return true;
|
||||
}
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get creation schema version: ", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean checkMuAdmin() {
|
||||
try {
|
||||
if (Case.CaseType.MULTI_USER_CASE == Case.getCurrentCaseThrows().getCaseType()) {
|
||||
return new File(ADMIN_ACCESS_FILE_PATH).exists() || new File(ADMIN_EXT_ACCESS_FILE_PATH).exists();
|
||||
}
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get the Create Major and Minor Schema Versions", ex);
|
||||
/**
|
||||
* Determines whether or not the delete data source action can be added.
|
||||
* @return True or false.
|
||||
*/
|
||||
private Boolean canAddDeleteDataSourceAction() {
|
||||
boolean canAddAction = false;
|
||||
CaseDbSchemaVersionNumber creationVersion = Case.getCurrentCase().getSleuthkitCase().getDBSchemaCreationVersion();
|
||||
if ((creationVersion.getMajor() == 8 && creationVersion.getMinor() >= 3) || (creationVersion.getMajor() > 8)) {
|
||||
canAddAction = Case.getCurrentCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE || !AccessLimiterUtils.limitMultiUserAccess();
|
||||
}
|
||||
return true;
|
||||
return canAddAction;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This property change listener refreshes the tree when a new file is
|
||||
* carved out of this image (i.e, the image is being treated as raw bytes
|
||||
|
Loading…
x
Reference in New Issue
Block a user