mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 10:17:41 +00:00
Merge pull request #5413 from sleuthkit/data-src-deletion
Merge data-src-deletion-branch into develop branch
This commit is contained in:
commit
be7107d3db
@ -50,7 +50,9 @@ public interface AutopsyService {
|
||||
* @param context The case context which includes things such as the case, a
|
||||
* progress indicator for the operation, a cancellation
|
||||
* request flag, etc.
|
||||
* @throws org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException
|
||||
*
|
||||
* @throws
|
||||
* org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException
|
||||
*/
|
||||
default void openCaseResources(CaseContext context) throws AutopsyServiceException {
|
||||
/*
|
||||
@ -64,7 +66,9 @@ public interface AutopsyService {
|
||||
* @param context The case context which includes things such as the case, a
|
||||
* progress indicator for the operation, a cancellation
|
||||
* request flag, etc.
|
||||
* @throws org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException
|
||||
*
|
||||
* @throws
|
||||
* org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException
|
||||
*/
|
||||
default void closeCaseResources(CaseContext context) throws AutopsyServiceException {
|
||||
/*
|
||||
@ -113,7 +117,9 @@ public interface AutopsyService {
|
||||
|
||||
/**
|
||||
* Gets the progress indicator for the creation/opening/upgrading of
|
||||
* case-level resources by a service.
|
||||
* case-level resources by a service. IMPORTANT: The service should only
|
||||
* call progress() on the progress indicator. Calling start() and
|
||||
* finish() are the responsibility of the case providing the context.
|
||||
*
|
||||
* @return The progress indicator.
|
||||
*/
|
||||
|
@ -1,49 +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.casemodule;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
|
||||
/**
|
||||
* Class for methods to check if access should be limited to a feature
|
||||
*
|
||||
*/
|
||||
final 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();
|
||||
|
||||
/**
|
||||
* Check if privileges regarding multi-user cases should be restricted.
|
||||
*
|
||||
* @return True if privileges should be restricted, false otherwise.
|
||||
*/
|
||||
static boolean limitMultiUserAccess() {
|
||||
return new File(MULTI_USER_ACCESS_FILE_PATH).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor for a utility class
|
||||
*/
|
||||
private AccessLimiterUtils() {
|
||||
//private constructer left empty intentionally
|
||||
}
|
||||
}
|
@ -2,14 +2,13 @@ AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules
|
||||
AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode
|
||||
# {0} - exception message
|
||||
Case.closeException.couldNotCloseCase=Error closing case: {0}
|
||||
Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory
|
||||
Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources
|
||||
Case.deleteCaseConfirmationDialog.message=Are you sure you want to close and delete the current case?
|
||||
Case.deleteCaseConfirmationDialog.title=Delete Current Case?
|
||||
# {0} - exception message
|
||||
Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}
|
||||
Case.deleteCaseFailureMessageBox.title=Failed to Delete Case
|
||||
Case.exceptionMessage.cancelledByUser=Cancelled by user.
|
||||
Case.exceptionMessage.cancelled=Cancelled.
|
||||
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.
|
||||
Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window
|
||||
@ -32,10 +31,13 @@ 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}.
|
||||
Case.exceptionMessage.dataSourceNotFound=The data source was not found.
|
||||
# {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.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.
|
||||
Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.
|
||||
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.execExceptionWrapperMessage={0}
|
||||
@ -50,11 +52,14 @@ Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.
|
||||
Case.exceptionMessage.metadataUpdateError=Failed to update case metadata
|
||||
# {0} - exception message
|
||||
Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.
|
||||
Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case.
|
||||
Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.
|
||||
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.progressIndicatorTitle.closingCase=Closing Case
|
||||
Case.progressIndicatorTitle.creatingCase=Creating Case
|
||||
Case.progressIndicatorTitle.deletingCase=Deleting Case
|
||||
Case.progressIndicatorTitle.deletingDataSource=Removing Data Source
|
||||
Case.progressIndicatorTitle.openingCase=Opening Case
|
||||
Case.progressMessage.cancelling=Cancelling...
|
||||
Case.progressMessage.clearingTempDirectory=Clearing case temp directory...
|
||||
@ -67,6 +72,7 @@ Case.progressMessage.creatingCaseNodeData=Creating coordination service node dat
|
||||
Case.progressMessage.deletingCaseDatabase=Deleting case database...
|
||||
Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node...
|
||||
Case.progressMessage.deletingCaseDirectory=Deleting case directory...
|
||||
Case.progressMessage.deletingDataSource=Removing the data source from the case...
|
||||
Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...
|
||||
Case.progressMessage.deletingTextIndex=Deleting text index...
|
||||
Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...
|
||||
@ -74,7 +80,6 @@ Case.progressMessage.openingApplicationServiceResources=Opening application serv
|
||||
Case.progressMessage.openingCaseDatabase=Opening case database...
|
||||
Case.progressMessage.openingCaseLevelServices=Opening case-level services...
|
||||
Case.progressMessage.preparing=Preparing...
|
||||
Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>
|
||||
Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu...
|
||||
Case.progressMessage.savingCaseMetadata=Saving case metadata to file...
|
||||
Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...
|
||||
@ -118,6 +123,13 @@ 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 remove the selected data source from the case?\nNote that the case will be closed and re-opened during the removal.
|
||||
# {0} - exception message
|
||||
DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to re-open the case:\n{0}\nPlease see the application log for details.
|
||||
# {0} - exception message
|
||||
DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while removing the data source:\n{0}\nPlease see the application log for details.
|
||||
DeleteDataSourceAction.ingestRunningWarningDialog.message=Data sources cannot be removed from a case when ingest is running.
|
||||
DeleteDataSourceAction.name.text=Remove 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.featureaccess.FeatureAccessUtils;
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
@ -77,6 +78,7 @@ import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
@ -121,11 +123,14 @@ import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.Report;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TimelineManager;
|
||||
import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskDataException;
|
||||
import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
|
||||
|
||||
/**
|
||||
@ -133,8 +138,8 @@ import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
|
||||
*/
|
||||
public class Case {
|
||||
|
||||
private static final int DIR_LOCK_TIMOUT_HOURS = 12;
|
||||
private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
|
||||
private static final int CASE_LOCK_TIMEOUT_MINS = 1;
|
||||
private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
|
||||
private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
|
||||
private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
|
||||
private static final String CACHE_FOLDER = "Cache"; //NON-NLS
|
||||
@ -153,13 +158,12 @@ public class Case {
|
||||
private static volatile Frame mainFrame;
|
||||
private static volatile Case currentCase;
|
||||
private final CaseMetadata metadata;
|
||||
private volatile ExecutorService caseLockingExecutor;
|
||||
private CoordinationService.Lock caseDirLock;
|
||||
private volatile ExecutorService caseActionExecutor;
|
||||
private CoordinationService.Lock caseLock;
|
||||
private SleuthkitCase caseDb;
|
||||
private final SleuthkitEventListener sleuthkitEventListener;
|
||||
private CollaborationMonitor collaborationMonitor;
|
||||
private Services caseServices;
|
||||
private boolean hasDataSources;
|
||||
private final TSKCaseRepublisher tskEventForwarder = new TSKCaseRepublisher();
|
||||
|
||||
/*
|
||||
* Get a reference to the main window of the desktop application to use to
|
||||
@ -167,11 +171,8 @@ public class Case {
|
||||
* changing the main window title.
|
||||
*/
|
||||
static {
|
||||
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mainFrame = WindowManager.getDefault().getMainWindow();
|
||||
}
|
||||
WindowManager.getDefault().invokeWhenUIReady(() -> {
|
||||
mainFrame = WindowManager.getDefault().getMainWindow();
|
||||
});
|
||||
}
|
||||
|
||||
@ -398,30 +399,37 @@ public class Case {
|
||||
* TimelineEvent that was added.
|
||||
*/
|
||||
TIMELINE_EVENT_ADDED,
|
||||
/* An item in the central repository has had its comment
|
||||
* modified. The old value is null, the new value is string for current
|
||||
* comment.
|
||||
/*
|
||||
* An item in the central repository has had its comment modified. The
|
||||
* old value is null, the new value is string for current comment.
|
||||
*/
|
||||
CR_COMMENT_CHANGED;
|
||||
|
||||
};
|
||||
|
||||
private final class TSKCaseRepublisher {
|
||||
/**
|
||||
* An instance of this class is registered as a listener on the event bus
|
||||
* associated with the case database so that selected SleuthKit layer
|
||||
* application events can be published as Autopsy application events.
|
||||
*/
|
||||
private final class SleuthkitEventListener {
|
||||
|
||||
@Subscribe
|
||||
public void rebroadcastTimelineEventCreated(TimelineManager.TimelineEventAddedEvent event) {
|
||||
public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
|
||||
eventPublisher.publish(new TimelineEventAddedEvent(event));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Subscribe
|
||||
public void rebroadcastArtifactsPosted(Blackboard.ArtifactsPostedEvent event) {
|
||||
public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) {
|
||||
for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) {
|
||||
/*
|
||||
* fireModuleDataEvent is deprecated so module writers don't use
|
||||
* it (they should use Blackboard.postArtifact(s) instead), but
|
||||
* we still need a way to rebroadcast the ArtifactsPostedEvent
|
||||
* as a ModuleDataEvent.
|
||||
* IngestServices.fireModuleDataEvent is deprecated to
|
||||
* discourage ingest module writers from using it (they should
|
||||
* use org.sleuthkit.datamodel.Blackboard.postArtifact(s)
|
||||
* instead), but a way to publish
|
||||
* Blackboard.ArtifactsPostedEvents from the SleuthKit layer as
|
||||
* Autopsy ModuleDataEvents is still needed.
|
||||
*/
|
||||
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
|
||||
event.getModuleName(),
|
||||
@ -535,8 +543,8 @@ public class Case {
|
||||
*/
|
||||
public static boolean isValidName(String caseName) {
|
||||
return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
|
||||
|| caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
|
||||
|| caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
|
||||
|| caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
|
||||
|| caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -668,14 +676,15 @@ public class Case {
|
||||
* method should put all operations in an exception firewall with a try and
|
||||
* catch-all block to handle the possibility of bad timing.
|
||||
*
|
||||
* TODO (JIRA-3825): Introduce a reference counting scheme for this get case
|
||||
* method.
|
||||
*
|
||||
* @return The current case.
|
||||
*
|
||||
* @throws NoCurrentCaseException if there is no current case.
|
||||
*/
|
||||
public static Case getCurrentCaseThrows() throws NoCurrentCaseException {
|
||||
/*
|
||||
* TODO (JIRA-3825): Introduce a reference counting scheme for this get
|
||||
* case method.
|
||||
*/
|
||||
Case openCase = currentCase;
|
||||
if (openCase == null) {
|
||||
throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
|
||||
@ -706,7 +715,7 @@ public class Case {
|
||||
eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
|
||||
logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
|
||||
currentCase = null;
|
||||
closedCase.close();
|
||||
closedCase.doCloseCaseAction();
|
||||
logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
|
||||
} catch (CaseActionException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
|
||||
@ -738,6 +747,46 @@ public class Case {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a data source from the current case.
|
||||
*
|
||||
* @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
|
||||
* and may be a wrapper for a lower-level
|
||||
* exception.
|
||||
*/
|
||||
@Messages({
|
||||
"Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
|
||||
})
|
||||
public static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
|
||||
synchronized (caseActionSerializationLock) {
|
||||
if (null == currentCase) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the current case to release the shared case lock.
|
||||
*/
|
||||
CaseMetadata caseMetadata = currentCase.getMetadata();
|
||||
closeCurrentCase();
|
||||
|
||||
/*
|
||||
* Re-open the case with an exclusive case lock, delete the data
|
||||
* source, and close the case again, releasing the exclusive case
|
||||
* lock.
|
||||
*/
|
||||
Case theCase = new Case(caseMetadata);
|
||||
theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
|
||||
|
||||
/*
|
||||
* Re-open the case with a shared case lock.
|
||||
*/
|
||||
openAsCurrentCase(new Case(caseMetadata), 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.
|
||||
@ -799,6 +848,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 {
|
||||
@ -816,7 +867,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> openCaseAction;
|
||||
if (isNewCase) {
|
||||
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
|
||||
openCaseAction = newCurrentCase::create;
|
||||
} else {
|
||||
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
|
||||
openCaseAction = newCurrentCase::open;
|
||||
}
|
||||
newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, 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()) {
|
||||
@ -978,14 +1038,14 @@ public class Case {
|
||||
@Messages({
|
||||
"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
|
||||
})
|
||||
private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
|
||||
private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
|
||||
try {
|
||||
Path caseDirPath = Paths.get(caseDir);
|
||||
String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
|
||||
Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
||||
Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
|
||||
return lock;
|
||||
} catch (InterruptedException ex) {
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
|
||||
} catch (CoordinationServiceException ex) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
|
||||
}
|
||||
@ -1049,7 +1109,7 @@ public class Case {
|
||||
/*
|
||||
* Enable the case-specific actions.
|
||||
*/
|
||||
CallableSystemAction.get(AddImageAction.class).setEnabled(Case.getCurrentCase().getMetadata().getCaseType() == CaseType.SINGLE_USER_CASE || !AccessLimiterUtils.limitMultiUserAccess());
|
||||
CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
|
||||
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
|
||||
@ -1366,16 +1426,14 @@ public class Case {
|
||||
/**
|
||||
* Gets the data sources for the case.
|
||||
*
|
||||
* @return A list of data sources.
|
||||
* @return A list of data sources, possibly empty.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException if there is a problem
|
||||
* querying the case
|
||||
* database.
|
||||
*/
|
||||
public List<Content> getDataSources() throws TskCoreException {
|
||||
List<Content> list = caseDb.getRootObjects();
|
||||
hasDataSources = (list.size() > 0);
|
||||
return list;
|
||||
return caseDb.getRootObjects();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1416,12 +1474,11 @@ public class Case {
|
||||
* @return True or false.
|
||||
*/
|
||||
public boolean hasData() {
|
||||
if (!hasDataSources) {
|
||||
try {
|
||||
hasDataSources = (getDataSources().size() > 0);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
|
||||
}
|
||||
boolean hasDataSources = false;
|
||||
try {
|
||||
hasDataSources = (getDataSources().size() > 0);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
|
||||
}
|
||||
return hasDataSources;
|
||||
}
|
||||
@ -1699,7 +1756,7 @@ public class Case {
|
||||
*
|
||||
*/
|
||||
private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
|
||||
metadata = new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails);
|
||||
this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1709,91 +1766,91 @@ public class Case {
|
||||
*/
|
||||
private Case(CaseMetadata caseMetaData) {
|
||||
metadata = caseMetaData;
|
||||
sleuthkitEventListener = new SleuthkitEventListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 that involves creating or opening a case. If the
|
||||
* case is a multi-user case, the action is done after acquiring a
|
||||
* coordination service case lock. This case lock must be released in the
|
||||
* same thread in which it was acquired, as required by the coordination
|
||||
* service. A single-threaded executor is therefore created to do the case
|
||||
* opening action, and is saved for an eventual case closing action.
|
||||
*
|
||||
* @param isNewCase True for a new case, false otherwise.
|
||||
* IMPORTANT: If an open case action for a multi-user case is terminated
|
||||
* because an exception is thrown or the action is cancelled, the action is
|
||||
* responsible for releasing the case lock while still running in the case
|
||||
* action executor's thread. This method assumes this has been done and
|
||||
* performs an orderly shut down of the case action executor.
|
||||
*
|
||||
* @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.
|
||||
* @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.
|
||||
*
|
||||
* @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>",
|
||||
"Case.progressMessage.cancelling=Cancelling...",
|
||||
"Case.exceptionMessage.cancelledByUser=Cancelled by user.",
|
||||
"Case.exceptionMessage.cancelled=Cancelled.",
|
||||
"# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
|
||||
})
|
||||
private void open(boolean isNewCase) throws CaseActionException {
|
||||
private void doOpenCaseAction(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 (with or without a
|
||||
* cancel button) 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
|
||||
* 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 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.
|
||||
* Do the case action in the single thread in the case action executor.
|
||||
* If the case is a multi-user case, a case lock is acquired and held
|
||||
* until explictly released and an exclusive case resources lock is
|
||||
* aquired and held for the duration of the action.
|
||||
*/
|
||||
TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
|
||||
caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
|
||||
Future<Void> future = caseLockingExecutor.submit(() -> {
|
||||
caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
|
||||
Future<Void> future = caseActionExecutor.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.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
|
||||
acquireSharedCaseDirLock(metadata.getCaseDirectory());
|
||||
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) {
|
||||
acquireCaseLock(caseLockType);
|
||||
try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(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());
|
||||
releaseCaseLock();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
@ -1804,44 +1861,31 @@ 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
|
||||
* 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.
|
||||
* The thread this method is running in has been interrupted.
|
||||
*/
|
||||
if (null != cancelButtonListener) {
|
||||
cancelButtonListener.actionPerformed(null);
|
||||
} else {
|
||||
future.cancel(true);
|
||||
}
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
|
||||
} catch (CancellationException discarded) {
|
||||
/*
|
||||
* The create/open 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.
|
||||
* The case action has been cancelled.
|
||||
*/
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
|
||||
ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
|
||||
} catch (ExecutionException ex) {
|
||||
/*
|
||||
* The create/open 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.
|
||||
* The case action has thrown an exception.
|
||||
*/
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
|
||||
} finally {
|
||||
progressIndicator.finish();
|
||||
@ -1849,55 +1893,48 @@ 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 create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
|
||||
assert (additionalParams == null);
|
||||
try {
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
createCaseDirectoryIfDoesNotExist(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
switchLoggingToCaseLogsDirectory(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
if (isNewCase) {
|
||||
saveCaseMetadataToFile(progressIndicator);
|
||||
}
|
||||
checkForUserCancellation();
|
||||
if (isNewCase) {
|
||||
createCaseNodeData(progressIndicator);
|
||||
} else {
|
||||
updateCaseNodeData(progressIndicator);
|
||||
}
|
||||
checkForUserCancellation();
|
||||
if (!isNewCase) {
|
||||
deleteTempfilesFromCaseDirectory(progressIndicator);
|
||||
}
|
||||
checkForUserCancellation();
|
||||
if (isNewCase) {
|
||||
createCaseDatabase(progressIndicator);
|
||||
} else {
|
||||
openCaseDataBase(progressIndicator);
|
||||
}
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
saveCaseMetadataToFile(progressIndicator);
|
||||
checkForCancellation();
|
||||
createCaseNodeData(progressIndicator);
|
||||
checkForCancellation();
|
||||
checkForCancellation();
|
||||
createCaseDatabase(progressIndicator);
|
||||
checkForCancellation();
|
||||
openCaseLevelServices(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
openAppServiceCaseResources(progressIndicator);
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
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.
|
||||
* Cancellation or failure. 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);
|
||||
@ -1908,6 +1945,102 @@ public class Case {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A case action (interface CaseAction<T, V, R>) that opens the case
|
||||
* database and application 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 open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
|
||||
assert (additionalParams == null);
|
||||
try {
|
||||
checkForCancellation();
|
||||
switchLoggingToCaseLogsDirectory(progressIndicator);
|
||||
checkForCancellation();
|
||||
updateCaseNodeData(progressIndicator);
|
||||
checkForCancellation();
|
||||
deleteTempfilesFromCaseDirectory(progressIndicator);
|
||||
checkForCancellation();
|
||||
openCaseDataBase(progressIndicator);
|
||||
checkForCancellation();
|
||||
openCaseLevelServices(progressIndicator);
|
||||
checkForCancellation();
|
||||
openAppServiceCaseResources(progressIndicator);
|
||||
checkForCancellation();
|
||||
openCommunicationChannels(progressIndicator);
|
||||
return null;
|
||||
|
||||
} catch (CaseActionException ex) {
|
||||
/*
|
||||
* Cancellation or failure. 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>) that opens a case, deletes
|
||||
* a data source from the case, and closes the case.
|
||||
*
|
||||
* @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.progressMessage.deletingDataSource=Removing the data source from the case...",
|
||||
"Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
|
||||
"Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
|
||||
"Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
|
||||
Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
|
||||
assert (additionalParams instanceof Long);
|
||||
open(progressIndicator, null);
|
||||
try {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
|
||||
Long dataSourceObjectID = (Long) additionalParams;
|
||||
try {
|
||||
DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
|
||||
if (dataSource == null) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
|
||||
}
|
||||
SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
|
||||
} catch (TskDataException | TskCoreException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
|
||||
}
|
||||
try {
|
||||
this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
|
||||
} catch (KeywordSearchServiceException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
|
||||
}
|
||||
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
|
||||
return null;
|
||||
} finally {
|
||||
close(progressIndicator);
|
||||
releaseCaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty portable case from the current case
|
||||
*
|
||||
@ -1954,18 +2087,15 @@ public class Case {
|
||||
* interrupted, assumes interrupt was
|
||||
* due to a user action.
|
||||
*/
|
||||
private static void checkForUserCancellation() throws CaseActionCancelledException {
|
||||
private static void checkForCancellation() throws CaseActionCancelledException {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
|
||||
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the case directory, if it does not already exist.
|
||||
*
|
||||
* TODO (JIRA-2180): Always create the case directory as part of the case
|
||||
* creation process.
|
||||
*
|
||||
* @param progressIndicator A progress indicator.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem completing the
|
||||
@ -1977,6 +2107,10 @@ public class Case {
|
||||
"Case.progressMessage.creatingCaseDirectory=Creating case directory..."
|
||||
})
|
||||
private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
|
||||
/*
|
||||
* TODO (JIRA-2180): Always create the case directory as part of the
|
||||
* case creation process.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
|
||||
if (new File(metadata.getCaseDirectory()).exists() == false) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
|
||||
@ -2166,6 +2300,7 @@ public class Case {
|
||||
} else {
|
||||
throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
|
||||
}
|
||||
caseDb.registerForEvents(sleuthkitEventListener);
|
||||
} catch (TskUnsupportedSchemaVersionException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
|
||||
} catch (UserPreferencesException ex) {
|
||||
@ -2186,8 +2321,6 @@ public class Case {
|
||||
private void openCaseLevelServices(ProgressIndicator progressIndicator) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
|
||||
this.caseServices = new Services(caseDb);
|
||||
|
||||
caseDb.registerForEvents(tskEventForwarder);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2217,7 +2350,9 @@ public class Case {
|
||||
* one starts by awaiting termination of the executor service.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
|
||||
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
|
||||
|
||||
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
|
||||
)) {
|
||||
/*
|
||||
* Create a progress indicator for the task and start the task. If
|
||||
* running with a GUI, the progress indicator will be a dialog box
|
||||
@ -2295,7 +2430,7 @@ public class Case {
|
||||
ThreadUtils.shutDownTaskExecutor(executor);
|
||||
appServiceProgressIndicator.finish();
|
||||
}
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2320,7 +2455,7 @@ public class Case {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
|
||||
try {
|
||||
eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
|
||||
checkForUserCancellation();
|
||||
checkForCancellation();
|
||||
collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
|
||||
} catch (AutopsyEventException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
|
||||
@ -2331,14 +2466,20 @@ public class Case {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the case.
|
||||
* Performs a case action that involves closing a case opened by calling
|
||||
* doOpenCaseAction. If the case is a multi-user case, the coordination
|
||||
* service case lock acquired by the call to doOpenCaseAction is released.
|
||||
* This case lock must be released in the same thread in which it was
|
||||
* acquired, as required by the coordination service. The single-threaded
|
||||
* executor saved during the case opening action is therefore used to do the
|
||||
* case closing action.
|
||||
*
|
||||
* @throws CaseActionException If there is a problem completing the
|
||||
* operation. 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 close() throws CaseActionException {
|
||||
private void doCloseCaseAction() throws CaseActionException {
|
||||
/*
|
||||
* Set up either a GUI progress indicator without a Cancel button or a
|
||||
* logging progress indicator.
|
||||
@ -2356,11 +2497,11 @@ public class Case {
|
||||
/*
|
||||
* Closing a case is always done in the same non-UI thread that
|
||||
* opened/created the case. If the case is a multi-user case, this
|
||||
* ensures that case directory lock that is 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.
|
||||
* ensures that case lock that is 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.
|
||||
*/
|
||||
Future<Void> future = caseLockingExecutor.submit(() -> {
|
||||
Future<Void> future = caseActionExecutor.submit(() -> {
|
||||
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
|
||||
close(progressIndicator);
|
||||
} else {
|
||||
@ -2370,7 +2511,7 @@ public class Case {
|
||||
* resources.
|
||||
*/
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_preparing());
|
||||
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) {
|
||||
try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
|
||||
if (null == resourcesLock) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
|
||||
}
|
||||
@ -2380,7 +2521,7 @@ public class Case {
|
||||
* Always release the case directory lock that was acquired
|
||||
* when the case was opened.
|
||||
*/
|
||||
releaseSharedCaseDirLock(metadata.getCaseName());
|
||||
releaseCaseLock();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -2392,13 +2533,13 @@ 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) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
|
||||
} finally {
|
||||
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
|
||||
ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
|
||||
progressIndicator.finish();
|
||||
}
|
||||
}
|
||||
@ -2440,7 +2581,7 @@ public class Case {
|
||||
*/
|
||||
if (null != caseDb) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
|
||||
caseDb.unregisterForEvents(tskEventForwarder);
|
||||
caseDb.unregisterForEvents(sleuthkitEventListener);
|
||||
caseDb.close();
|
||||
}
|
||||
|
||||
@ -2464,7 +2605,8 @@ public class Case {
|
||||
* Each service gets its own independently cancellable task, and thus
|
||||
* its own task progress indicator.
|
||||
*/
|
||||
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
|
||||
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
|
||||
)) {
|
||||
ProgressIndicator progressIndicator;
|
||||
if (RuntimeProperties.runningWithGUI()) {
|
||||
progressIndicator = new ModalDialogProgressIndicator(
|
||||
@ -2504,37 +2646,47 @@ public class Case {
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a shared case directory lock for the current case.
|
||||
* Acquires a case (case directory) lock for the current case.
|
||||
*
|
||||
* @param caseDir The full path of the case directory.
|
||||
*
|
||||
* @throws CaseActionException with a user-friendly message if the lock
|
||||
* cannot be acquired.
|
||||
* @throws CaseActionException If the lock cannot be acquired.
|
||||
*/
|
||||
@Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory"})
|
||||
private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
|
||||
@Messages({
|
||||
"Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.",
|
||||
"Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case."
|
||||
})
|
||||
private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
|
||||
String caseDir = metadata.getCaseDirectory();
|
||||
try {
|
||||
caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
|
||||
if (null == caseDirLock) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
caseLock = lockType == CaseLockType.SHARED
|
||||
? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
|
||||
: coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
|
||||
if (caseLock == null) {
|
||||
if (lockType == CaseLockType.SHARED) {
|
||||
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
|
||||
} else {
|
||||
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | CoordinationServiceException ex) {
|
||||
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
|
||||
if (lockType == CaseLockType.SHARED) {
|
||||
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
|
||||
} else {
|
||||
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a shared case directory lock for the current case.
|
||||
*
|
||||
* @param caseDir The full path of the case directory.
|
||||
* Releases a case (case directory) lock for the current case.
|
||||
*/
|
||||
private void releaseSharedCaseDirLock(String caseDir) {
|
||||
if (caseDirLock != null) {
|
||||
private void releaseCaseLock() {
|
||||
if (caseLock != null) {
|
||||
try {
|
||||
caseDirLock.release();
|
||||
caseDirLock = null;
|
||||
caseLock.release();
|
||||
caseLock = null;
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
|
||||
logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2807,7 +2959,9 @@ public class Case {
|
||||
})
|
||||
private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
|
||||
for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
|
||||
|
||||
for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
|
||||
)) {
|
||||
searchService.deleteTextIndex(metadata);
|
||||
}
|
||||
}
|
||||
@ -2908,9 +3062,41 @@ public class Case {
|
||||
CaseNodeData.writeCaseNodeData(caseNodeData);
|
||||
} catch (CaseNodeDataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the signature for case action methods that can be passed as
|
||||
* arguments to the doCaseAction method.
|
||||
*
|
||||
* @param <T> A ProgressIndicator
|
||||
* @param <V> The optional parameters stored in an Object.
|
||||
* @param <R> The return type of Void.
|
||||
*/
|
||||
private interface CaseAction<T, V, R> {
|
||||
|
||||
/**
|
||||
* The signature for a case action method.
|
||||
*
|
||||
* @param progressIndicator A ProgressIndicator.
|
||||
* @param additionalParams The optional parameters stored in an Object.
|
||||
*
|
||||
* @return A Void object (null).
|
||||
*
|
||||
* @throws CaseActionException
|
||||
*/
|
||||
R execute(T progressIndicator, V additionalParams) throws CaseActionException;
|
||||
}
|
||||
|
||||
/**
|
||||
* The choices for the case (case directory) coordination service lock used
|
||||
* for multi-user cases.
|
||||
*/
|
||||
private enum CaseLockType {
|
||||
SHARED, EXCLUSIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A case operation Cancel button listener for use with a
|
||||
* ModalDialogProgressIndicator when running with a GUI.
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
|
||||
/**
|
||||
* An Action that allows a user to remove 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 remove a data source from a
|
||||
* case.
|
||||
*
|
||||
* @param dataSourceObjectID The object ID of the data source to be removed.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"DeleteDataSourceAction.name.text=Remove Data Source"
|
||||
})
|
||||
public DeleteDataSourceAction(Long dataSourceObjectID) {
|
||||
super(Bundle.DeleteDataSourceAction_name_text());
|
||||
this.dataSourceObjectID = dataSourceObjectID;
|
||||
this.setEnabled(FeatureAccessUtils.canDeleteDataSources());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DeleteDataSourceAction.ingestRunningWarningDialog.message=Data sources cannot be removed from a case when ingest is running.",
|
||||
"DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to remove the selected data source from the case?\nNote that the case will be closed and re-opened during the removal.",
|
||||
"# {0} - exception message", "DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while removing the data source:\n{0}\nPlease see the application log for details.",
|
||||
"# {0} - exception message", "DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to re-open the case:\n{0}\nPlease see the application log for details."
|
||||
})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (IngestManager.getInstance().isIngestRunning()) {
|
||||
MessageNotifyUtil.Message.warn(Bundle.DeleteDataSourceAction_ingestRunningWarningDialog_message());
|
||||
return;
|
||||
}
|
||||
|
||||
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteDataSourceAction_confirmationDialog_message())) {
|
||||
new DataSourceDeletionWorker().execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SwingWorker to do the data source deletion.
|
||||
*/
|
||||
private class DataSourceDeletionWorker extends SwingWorker<Void, Void> {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
/*
|
||||
* Save the case metadata file path so the case can be reopened if
|
||||
* something goes wrong and the case ends up closed.
|
||||
*/
|
||||
caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath();
|
||||
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(ex.getLocalizedMessage()), MessageNotifyUtil.MessageType.ERROR);
|
||||
if (!Case.isCaseOpen()) {
|
||||
new CaseReopeningWorker().execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A SwingWorker to attempt to re-open the case after a data source deletion
|
||||
* exception.
|
||||
*/
|
||||
private class CaseReopeningWorker extends SwingWorker<Void, Void> {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
Case.openAsCurrentCase(caseMetadataFilePath.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Interrupted reopening case after error deleting data source (obj_id=%d)", dataSourceObjectID), ex);
|
||||
|
||||
} catch (ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error reopening case after error deleting data source (obj_id=%d)", dataSourceObjectID), ex);
|
||||
MessageNotifyUtil.Message.show(Bundle.DeleteDataSourceAction_exceptionMessage_dataSourceDeletionError(ex.getCause().getLocalizedMessage()), MessageNotifyUtil.MessageType.ERROR);
|
||||
StartupWindowProvider.getInstance().open();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteDataSourceAction clone() throws CloneNotSupportedException {
|
||||
DeleteDataSourceAction clonedObject = ((DeleteDataSourceAction) super.clone());
|
||||
clonedObject.setDataSourceID(this.dataSourceObjectID);
|
||||
return clonedObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the setting of the data source object ID field of a clone of this
|
||||
* action.
|
||||
*
|
||||
* @param dataSourceObjectID The data source object ID.
|
||||
*/
|
||||
private void setDataSourceID(long dataSourceObjectID) {
|
||||
this.dataSourceObjectID = dataSourceObjectID;
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
|
||||
import java.awt.Component;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
@ -61,7 +62,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
|
||||
*/
|
||||
void readSettings() {
|
||||
caseNameTextField.setText("");
|
||||
if (UserPreferences.getIsMultiUserModeEnabled() && !AccessLimiterUtils.limitMultiUserAccess()) {
|
||||
if (FeatureAccessUtils.canCreateMultiUserCases()) {
|
||||
multiUserCaseRadioButton.setEnabled(true);
|
||||
multiUserCaseRadioButton.setSelected(true);
|
||||
} else {
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
|
||||
/**
|
||||
* An application event that is published when a data source has been deleted.
|
||||
*/
|
||||
public class DataSourceDeletedEvent extends AutopsyEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final long dataSourceID;
|
||||
|
||||
/**
|
||||
* Constructs an application event that is published when a data source has
|
||||
* been deleted.
|
||||
*
|
||||
* @param dataSourceId The object ID of the data source that was deleted.
|
||||
*/
|
||||
public DataSourceDeletedEvent(Long dataSourceId) {
|
||||
super(Case.Events.DATA_SOURCE_DELETED.toString(), dataSourceId, null);
|
||||
this.dataSourceID = dataSourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object ID of the data source that was deleted.
|
||||
*
|
||||
* @return The data source id.
|
||||
*/
|
||||
public long getDataSourceId() {
|
||||
return dataSourceID;
|
||||
}
|
||||
|
||||
}
|
@ -103,8 +103,8 @@ public class Services implements Closeable {
|
||||
/**
|
||||
* Closes the services for the current case.
|
||||
*
|
||||
* @throws IOException if there is a problem closing the services.
|
||||
* @deprecated Do not use.
|
||||
* @throws IOException if there is a problem closing the services.
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
|
@ -5,10 +5,7 @@ CentralRepoCommentDialog.title.addEditCentralRepoComment=Add/Edit Central Reposi
|
||||
OpenIDE-Module-Name=Central Repository
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Short-Description=Correlation Engine Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Correlation Engine ingest module and central database. \n\n\
|
||||
The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\
|
||||
Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
|
||||
OpenIDE-Module-Long-Description=Correlation Engine ingest module and central database. \n\nThe Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\nStored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
|
||||
CentralRepoCommentDialog.commentLabel.text=Comment:
|
||||
CentralRepoCommentDialog.okButton.text=&OK
|
||||
CentralRepoCommentDialog.cancelButton.text=C&ancel
|
||||
|
@ -3,13 +3,7 @@ Installer.closing.confirmationDialog.title=Ingest is Running
|
||||
# {0} - exception message
|
||||
Installer.closing.messageBox.caseCloseExceptionMessage=Error closing case: {0}
|
||||
OpenIDE-Module-Display-Category=Infrastructure
|
||||
OpenIDE-Module-Long-Description=\
|
||||
This is the core Autopsy module.\n\n\
|
||||
The module contains the core components needed for the bare application to run; the RCP platform, windowing GUI, sleuthkit bindings, datamodel / storage, explorer, result viewers, content viewers, ingest framework, reporting, and core tools, such as the file search.\n\n\
|
||||
The framework included in the module contains APIs for developing modules for ingest, viewers and reporting. \
|
||||
The modules can be deployed as Plugins using the Autopsy plugin installer.\n\
|
||||
This module should not be uninstalled - without it, Autopsy will not run.\n\n\
|
||||
For more information, see http://www.sleuthkit.org/autopsy/
|
||||
OpenIDE-Module-Long-Description=This is the core Autopsy module.\n\nThe module contains the core components needed for the bare application to run; the RCP platform, windowing GUI, sleuthkit bindings, datamodel / storage, explorer, result viewers, content viewers, ingest framework, reporting, and core tools, such as the file search.\n\nThe framework included in the module contains APIs for developing modules for ingest, viewers and reporting. The modules can be deployed as Plugins using the Autopsy plugin installer.\nThis module should not be uninstalled - without it, Autopsy will not run.\n\nFor more information, see http://www.sleuthkit.org/autopsy/
|
||||
OpenIDE-Module-Name=Autopsy-Core
|
||||
OpenIDE-Module-Short-Description=Autopsy Core Module
|
||||
org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml
|
||||
|
@ -63,9 +63,9 @@ DataContentViewerHex.totalPageLabel.text_1=100
|
||||
DataContentViewerHex.pageLabel2.text=Page
|
||||
|
||||
# Product Information panel
|
||||
LBL_Description=<div style=\"font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif;\">\n <b>Product Version:</b> {0} ({9}) <br><b>Sleuth Kit Version:</b> {7} <br><b>Netbeans RCP Build:</b> {8} <br> <b>Java:</b> {1}; {2}<br> <b>System:</b> {3}; {4}; {5}<br><b>Userdir:</b> {6}</div>
|
||||
LBL_Description=<div style="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif;">\n <b>Product Version:</b> {0} ({9}) <br><b>Sleuth Kit Version:</b> {7} <br><b>Netbeans RCP Build:</b> {8} <br> <b>Java:</b> {1}; {2}<br> <b>System:</b> {3}; {4}; {5}<br><b>Userdir:</b> {6}</div>
|
||||
Format_OperatingSystem_Value={0} version {1} running on {2}
|
||||
LBL_Copyright=<div style\="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif; ">Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools. <br><ul><li>General Information: <a style\="color: \#1E2A60;" href\="http://www.sleuthkit.org">http://www.sleuthkit.org</a>.</li><li>Training: <a style\="color: \#1E2A60;" href\="http://www.basistech.com/autopsy-training">http://www.basistech.com/autopsy-training</a></li><li>Commercial Support: <a style\="color: \#1E2A60;" href\="http://www.basistech.com/digital-forensics/autopsy/support/">http://www.basistech.com/digital-forensics/autopsy/support/</a></li></ul>Copyright © 2003-2018. </div>
|
||||
LBL_Copyright=<div style="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif; ">Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools. <br><ul><li>General Information: <a style="color: #1E2A60;" href="http://www.sleuthkit.org">http://www.sleuthkit.org</a>.</li><li>Training: <a style="color: #1E2A60;" href="http://www.basistech.com/autopsy-training">http://www.basistech.com/autopsy-training</a></li><li>Commercial Support: <a style="color: #1E2A60;" href="http://www.basistech.com/digital-forensics/autopsy/support/">http://www.basistech.com/digital-forensics/autopsy/support/</a></li></ul>Copyright © 2003-2018. </div>
|
||||
SortChooser.dialogTitle=Choose Sort Criteria
|
||||
ThumbnailViewChildren.progress.cancelling=(Cancelling)
|
||||
# {0} - file name
|
||||
@ -95,7 +95,7 @@ DataResultViewerThumbnail.pageNextButton.text=
|
||||
DataResultViewerThumbnail.imagesLabel.text=Images:
|
||||
DataResultViewerThumbnail.imagesRangeLabel.text=-
|
||||
DataResultViewerThumbnail.pageNumLabel.text=-
|
||||
DataResultViewerThumbnail.filePathLabel.text=\ \ \
|
||||
DataResultViewerThumbnail.filePathLabel.text=\
|
||||
DataResultViewerThumbnail.goToPageLabel.text=Go to Page:
|
||||
DataResultViewerThumbnail.goToPageField.text=
|
||||
AdvancedConfigurationDialog.cancelButton.text=Cancel
|
||||
|
@ -30,9 +30,7 @@ PlatformUtil.getProcVmUsed.sigarNotInit.msg=Cannot get virt mem used, sigar not
|
||||
PlatformUtil.getProcVmUsed.gen.msg=Cannot get virt mem used, {0}
|
||||
PlatformUtil.getJvmMemInfo.usageText=JVM heap usage: {0}, JVM non-heap usage: {1}
|
||||
PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free): {0}, {1}, {2}
|
||||
PlatformUtil.getAllMemUsageInfo.usageText={0}\n\
|
||||
{1}\n\
|
||||
Process Virtual Memory: {2}
|
||||
PlatformUtil.getAllMemUsageInfo.usageText={0}\n{1}\nProcess Virtual Memory: {2}
|
||||
# {0} - file name
|
||||
ReadImageTask.mesageText=Reading image: {0}
|
||||
StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract
|
||||
|
@ -44,7 +44,7 @@ final public class ThreadUtils {
|
||||
} catch (InterruptedException ignored) {
|
||||
/*
|
||||
* Ignore interrupts. The policy implemented by this method is
|
||||
* an unconditional wait.:
|
||||
* an unconditional wait.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ ArtifactStringContent.attrsTableHeader.type=Type
|
||||
ArtifactStringContent.attrsTableHeader.value=Value
|
||||
ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database
|
||||
ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database
|
||||
# {0} - node name
|
||||
BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}
|
||||
BlackboardArtifactNode.createSheet.artifactDetails.displayName=Result Details
|
||||
BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details
|
||||
@ -264,10 +263,10 @@ ImageNode.getActions.viewInNewWin.text=View in New Window
|
||||
ImageNode.createSheet.name.name=Name
|
||||
ImageNode.createSheet.name.displayName=Name
|
||||
ImageNode.createSheet.name.desc=no description
|
||||
Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null\!
|
||||
Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""\!
|
||||
Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n\nDetails: {0}
|
||||
Installer.tskLibErr.err=Fatal Error\!
|
||||
Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null!
|
||||
Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""!
|
||||
Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed!\n\nDetails: {0}
|
||||
Installer.tskLibErr.err=Fatal Error!
|
||||
InterestingHits.interestingItems.text=INTERESTING ITEMS
|
||||
InterestingHits.displayName.text=Interesting Items
|
||||
InterestingHits.createSheet.name.name=Name
|
||||
|
@ -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");
|
||||
@ -34,6 +34,7 @@ import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
@ -105,7 +106,7 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
*/
|
||||
@Override
|
||||
@Messages({"ImageNode.action.runIngestMods.text=Run Ingest Modules",
|
||||
"ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",})
|
||||
"ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes"})
|
||||
public Action[] getActions(boolean context) {
|
||||
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
@ -113,13 +114,12 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
actionsList.add(a);
|
||||
}
|
||||
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
|
||||
actionsList.add(new FileSearchAction(
|
||||
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
|
||||
actionsList.add(new FileSearchAction(Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
|
||||
actionsList.add(new ViewSummaryInformationAction(content.getId()));
|
||||
actionsList.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
|
||||
actionsList.add(new NewWindowViewAction(
|
||||
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
|
||||
actionsList.add(new DeleteDataSourceAction(content.getId()));
|
||||
return actionsList.toArray(new Action[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.featureaccess;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
|
||||
|
||||
/**
|
||||
* Check if access to various features is permitted for the current user and the
|
||||
* current case, if any.
|
||||
*
|
||||
* IMPORTANT: These utilities are not concerned with transitory restrictions on
|
||||
* access to a feature, e.g., whether or not ingest is running.
|
||||
*/
|
||||
final public class FeatureAccessUtils {
|
||||
|
||||
private final static String MULTIUSER_CASE_RESTRICTED_FILE_NAME = "mualimit"; // NON-NLS
|
||||
private final static String MULTIUSER_CASE_RESTRICTED_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), MULTIUSER_CASE_RESTRICTED_FILE_NAME).toString();
|
||||
private static final int DATA_SRC_DEL_MIN_DB_MAJOR_VER = 8;
|
||||
private static final int DATA_SRC_DEL_MIN_DB_MINOR_VER = 4;
|
||||
|
||||
/**
|
||||
* Indicates whether or not a user can create multi-user cases.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean canCreateMultiUserCases() {
|
||||
return UserPreferences.getIsMultiUserModeEnabled() && multiUserCaseRestrictionsFileAbsent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not a user can add data sources to a case.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean canAddDataSources() {
|
||||
return currentCaseIsSingleUserCase() || multiUserCaseRestrictionsFileAbsent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not a user can delete data sources from a case.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean canDeleteDataSources() {
|
||||
boolean dataSourceDeletionAllowed = false;
|
||||
if (Case.isCaseOpen()) {
|
||||
CaseDbSchemaVersionNumber version = Case.getCurrentCase().getSleuthkitCase().getDBSchemaCreationVersion();
|
||||
dataSourceDeletionAllowed
|
||||
= ((version.getMajor() > DATA_SRC_DEL_MIN_DB_MAJOR_VER) || (version.getMajor() == DATA_SRC_DEL_MIN_DB_MAJOR_VER && version.getMinor() >= DATA_SRC_DEL_MIN_DB_MINOR_VER))
|
||||
&& (currentCaseIsSingleUserCase() || multiUserCaseRestrictionsFileAbsent());
|
||||
}
|
||||
return dataSourceDeletionAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the current case is a single-user case.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
private static boolean currentCaseIsSingleUserCase() {
|
||||
return Case.isCaseOpen() && Case.getCurrentCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not the current user is allowed to create or modify
|
||||
* (add or delete data sources) multi-user cases.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
public static boolean multiUserCaseRestrictionsFileAbsent() {
|
||||
File accessLimitingFile = new File(MULTIUSER_CASE_RESTRICTED_FILE_PATH);
|
||||
return !accessLimitingFile.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private FeatureAccessUtils() {
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ KnownStatusSearchPanel.knownCheckBox.text=Known Status:
|
||||
KnownStatusSearchPanel.knownBadOptionCheckBox.text=Notable
|
||||
KnownStatusSearchPanel.knownOptionCheckBox.text=Known (NSRL or other)
|
||||
KnownStatusSearchPanel.unknownOptionCheckBox.text=Unknown
|
||||
DateSearchFilter.noneSelectedMsg.text=At least one date type must be selected\!
|
||||
DateSearchFilter.noneSelectedMsg.text=At least one date type must be selected!
|
||||
DateSearchPanel.dateCheckBox.text=Date:
|
||||
DateSearchPanel.jLabel4.text=Timezone:
|
||||
DateSearchPanel.jLabel3.text=*The date format is mm/dd/yyyy
|
||||
@ -56,7 +56,7 @@ FileSearchPanel.search.results.details=Large number of matches may impact perfor
|
||||
FileSearchPanel.search.exception.noFilterSelected.msg=At least one filter must be selected.
|
||||
FileSearchPanel.search.validationErr.msg=Validation Error: {0}
|
||||
FileSearchPanel.emptyWhereClause.text=Invalid options, nothing to show.
|
||||
KnownStatusSearchFilter.noneSelectedMsg.text=At least one known status must be selected\!
|
||||
KnownStatusSearchFilter.noneSelectedMsg.text=At least one known status must be selected!
|
||||
NameSearchFilter.emptyNameMsg.text=Must enter something for name search.
|
||||
SearchNode.getName.text=Search Result
|
||||
SizeSearchPanel.sizeCompareComboBox.equalTo=equal to
|
||||
|
@ -140,7 +140,7 @@ IngestJob.cancelReason.outOfDiskSpace.text=Out of disk space
|
||||
IngestJob.cancelReason.servicesDown.text=Services Down
|
||||
IngestJob.cancelReason.caseClosed.text=Case closed
|
||||
IngestJobSettingsPanel.globalSettingsButton.text=Global Settings
|
||||
gest
|
||||
gest=
|
||||
IngestJobSettingsPanel.globalSettingsButton.actionCommand=Advanced
|
||||
IngestJobSettingsPanel.globalSettingsButton.text=Global Settings
|
||||
IngestJobSettingsPanel.pastJobsButton.text=History
|
||||
|
@ -110,7 +110,6 @@ public final class IngestServices {
|
||||
@Deprecated
|
||||
public void fireModuleDataEvent(ModuleDataEvent moduleDataEvent) {
|
||||
IngestManager.getInstance().fireIngestModuleDataEvent(moduleDataEvent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,11 +28,11 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
/**
|
||||
* An interface for implementations of a keyword search service. You can find
|
||||
* the implementations by using Lookup, such as:
|
||||
*
|
||||
*
|
||||
* Lookup.getDefault().lookup(KeywordSearchService.class)
|
||||
*
|
||||
*
|
||||
* although most clients should obtain a keyword search service by calling:
|
||||
*
|
||||
*
|
||||
* Case.getCurrentCase().getServices().getKeywordSearchService()
|
||||
*
|
||||
* TODO (AUT-2158): This interface should not extend Closeable.
|
||||
@ -82,7 +82,7 @@ public interface KeywordSearchService extends Closeable {
|
||||
* @throws KeywordSearchServiceException if unable to delete.
|
||||
*/
|
||||
public void deleteTextIndex(CaseMetadata metadata) throws KeywordSearchServiceException;
|
||||
|
||||
|
||||
/**
|
||||
* Closes the keyword search service.
|
||||
*
|
||||
@ -95,6 +95,15 @@ public interface KeywordSearchService extends Closeable {
|
||||
* No-op maintained for backwards compatibility. Clients should not
|
||||
* attempt to close case services.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the keyword search text for a specific data source.
|
||||
*
|
||||
* @param dataSourceId The data source id to be deleted.
|
||||
*
|
||||
* @throws KeywordSearchServiceException if unable to delete.
|
||||
*/
|
||||
void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException;
|
||||
|
||||
}
|
||||
|
@ -11,12 +11,7 @@ ExtractArchiveWithPasswordAction.progress.text=Unpacking contents of archive: {0
|
||||
ExtractArchiveWithPasswordAction.prompt.text=Enter Password
|
||||
ExtractArchiveWithPasswordAction.prompt.title=Enter Password
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Embedded File Extraction Ingest Module\n\nThe Embedded File Extraction Ingest Module processes document files (such as doc, docx, ppt, pptx, xls, xlsx) and archive files (such as zip and others archive types supported by the 7zip extractor).\n\
|
||||
Contents of these files are extracted and the derived files are added back to the current ingest to be processed by the configured ingest modules.\n\
|
||||
If the derived file happens to be an archive file, it will be re-processed by the 7zip extractor - the extractor will process archive files N-levels deep.\n\n\
|
||||
The extracted files are navigable in the directory tree.\n\n\
|
||||
The module is supported on Windows, Linux and Mac operating systems.
|
||||
OpenIDE-Module-Long-Description=Embedded File Extraction Ingest Module\n\nThe Embedded File Extraction Ingest Module processes document files (such as doc, docx, ppt, pptx, xls, xlsx) and archive files (such as zip and others archive types supported by the 7zip extractor).\nContents of these files are extracted and the derived files are added back to the current ingest to be processed by the configured ingest modules.\nIf the derived file happens to be an archive file, it will be re-processed by the 7zip extractor - the extractor will process archive files N-levels deep.\n\nThe extracted files are navigable in the directory tree.\n\nThe module is supported on Windows, Linux and Mac operating systems.
|
||||
OpenIDE-Module-Name=Embedded File Extraction
|
||||
OpenIDE-Module-Short-Description=Embedded File Extraction Ingest Module
|
||||
EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.seek.exception.invalidOrigin=Invalid seek origin: {0}
|
||||
|
@ -1,9 +1,7 @@
|
||||
CannotRunFileTypeDetection=Cannot run file type detection.
|
||||
ExifParserFileIngestModule.indexError.message=Failed to post EXIF Metadata artifact(s).
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Exif metadata ingest module. \n\n\
|
||||
The ingest module analyzes image files, extracts Exif information and posts the Exif data as results.
|
||||
OpenIDE-Module-Long-Description=Exif metadata ingest module. \n\nThe ingest module analyzes image files, extracts Exif information and posts the Exif data as results.
|
||||
OpenIDE-Module-Name=ExifParser
|
||||
OpenIDE-Module-Short-Description=Exif metadata ingest module
|
||||
ExifParserFileIngestModule.moduleName.text=Exif Parser
|
||||
|
@ -36,27 +36,27 @@ FileExtMismatchSettingsPanel.jLabel1.text=File Types:
|
||||
FileExtMismatchSettingsPanel.newExtButton.text=New Extension
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.message=Add a new MIME file type:
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.title=New MIME
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.message=MIME type text is empty\!
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.message=MIME type text is empty!
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.title=Empty type
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.message=MIME type not supported\!
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.message=MIME type not supported!
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.title=Type not supported
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.message=MIME type already exists\!
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.message=MIME type already exists!
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.title=Type already exists
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.message=MIME type is not detectable by this module.
|
||||
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.title=Type not detectable
|
||||
FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.message=No MIME type selected\!
|
||||
FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.message=No MIME type selected!
|
||||
FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.title=No type selected
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.message=Add an allowed extension:
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.title=New allowed extension
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.empty.message=Extension text is empty\!
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.empty.message=Extension text is empty!
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.empty.title=Extension text empty
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.message=No MIME type selected\!
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.message=No MIME type selected!
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.title=No MIME type selected
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.extExists.message=Extension already exists\!
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.extExists.message=Extension already exists!
|
||||
FileExtMismatchSettingsPanel.newExtPrompt.extExists.title=Extension already exists
|
||||
FileExtMismatchSettingsPanel.removeExtButton.noneSelected.message=No extension selected\!
|
||||
FileExtMismatchSettingsPanel.removeExtButton.noneSelected.message=No extension selected!
|
||||
FileExtMismatchSettingsPanel.removeExtButton.noneSelected.title=No extension selected
|
||||
FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.message=No MIME type selected\!
|
||||
FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.message=No MIME type selected!
|
||||
FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.title=No MIME type selected
|
||||
FileExtMismatchSettingsPanel.removeTypeButton.toolTipText=
|
||||
FileExtMismatchModuleSettingsPanel.checkAllRadioButton.text=Check all file types
|
||||
|
@ -49,10 +49,7 @@ ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash se
|
||||
ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed
|
||||
ImportCentralRepoDbProgressDialog.title.text=Central Repository Import Progress
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Hash Set ingest module. \n\n\
|
||||
The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\n\
|
||||
The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration.
|
||||
OpenIDE-Module-Long-Description=Hash Set ingest module. \n\nThe ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\nThe module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration.
|
||||
OpenIDE-Module-Name=HashDatabases
|
||||
OptionsCategory_Name_HashDatabase=Hash Sets
|
||||
OptionsCategory_Keywords_HashDatabase=Hash Sets
|
||||
@ -181,10 +178,7 @@ HashDbSearchThread.name.searching=Searching
|
||||
HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found.
|
||||
ModalNoButtons.indexingDbsTitle=Indexing hash sets
|
||||
ModalNoButtons.indexingDbTitle=Indexing hash set
|
||||
ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \n\
|
||||
The generated index will be left unusable. If you choose to continue,\n\
|
||||
please delete the corresponding -md5.idx file in the hash folder.\n\
|
||||
Exit indexing?
|
||||
ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \nThe generated index will be left unusable. If you choose to continue,\nplease delete the corresponding -md5.idx file in the hash folder.\nExit indexing?
|
||||
ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing
|
||||
ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set
|
||||
ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0}
|
||||
|
@ -83,8 +83,8 @@ FilesSetRulePanel.nameTextField.text=
|
||||
FilesSetRulePanel.ruleNameLabel.text=Rule Name (Optional):
|
||||
FilesSetRulePanel.messages.emptyNameCondition=You must specify a name pattern for this rule.
|
||||
FilesSetRulePanel.messages.invalidNameRegex=The name regular expression is not valid:\n\n{0}
|
||||
FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, \", <, or > unless it is a regular expression.
|
||||
FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, \", <, or > unless it is a regular expression.
|
||||
FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, ", <, or > unless it is a regular expression.
|
||||
FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, ", <, or > unless it is a regular expression.
|
||||
FilesSetRulePanel.messages.invalidPathRegex=The path regular expression is not valid:\n\n{0}
|
||||
FilesSetDefsPanel.doFileSetsDialog.duplicateRuleSet.text=Rule set with name {0} already exists.
|
||||
FilesSetRulePanel.pathSeparatorInfoLabel.text=Folder must be in parent path. Use '/' to give consecutive names
|
||||
|
@ -21,7 +21,7 @@ PhotoRecIngestModule.complete.totalParsetime=Total Parsing Time:
|
||||
PhotoRecIngestModule.complete.photoRecResults=PhotoRec Results
|
||||
PhotoRecIngestModule.NotEnoughDiskSpace.detail.msg=PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space.
|
||||
PhotoRecIngestModule.cancelledByUser=PhotoRec cancelled by user.
|
||||
PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value \= {0} when scanning {1}
|
||||
PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value = {0} when scanning {1}
|
||||
PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver.
|
||||
PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving:
|
||||
PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings
|
||||
|
@ -5,8 +5,8 @@ ReportHTML.getName.text=HTML Report
|
||||
ReportHTML.getDesc.text=A report about results and tagged items in HTML format.
|
||||
ReportHTML.writeIndex.title=for case {0}
|
||||
ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup.
|
||||
ReportHTML.writeIndex.noFrames.seeNav=Please see <a href\="content\nav.html">the navigation page</a> for artifact links,
|
||||
ReportHTML.writeIndex.seeSum=and <a href\="content\summary.html">the summary page</a> for a case summary.
|
||||
ReportHTML.writeIndex.noFrames.seeNav=Please see <a href="content\nav.html">the navigation page</a> for artifact links,
|
||||
ReportHTML.writeIndex.seeSum=and <a href="contentsummary.html">the summary page</a> for a case summary.
|
||||
ReportHTML.writeNav.title=Report Navigation
|
||||
ReportHTML.writeNav.h1=Report Navigation
|
||||
ReportHTML.writeNav.summary=Case Summary
|
||||
@ -16,7 +16,7 @@ ReportHTML.writeSum.caseNumber=Case Number:
|
||||
ReportHTML.writeSum.caseNumImages=Number of Images:
|
||||
ReportHTML.writeSum.examiner=Examiner:
|
||||
ReportHTML.writeSum.title=Case Summary
|
||||
ReportHTML.writeSum.warningMsg=<span>Warning, this report was run before ingest services completed\!</span>
|
||||
ReportHTML.writeSum.warningMsg=<span>Warning, this report was run before ingest services completed!</span>
|
||||
#
|
||||
# autopsy/test/scripts/regression.py._html_report_diff() uses reportGenOn.text, caseName, caseNum,
|
||||
# examiner as a regex signature to skip report.html and summary.html
|
||||
|
@ -23,6 +23,7 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.SQLException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -55,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.History;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
@ -102,7 +104,8 @@ public final class ImageGalleryController {
|
||||
Case.Events.CURRENT_CASE,
|
||||
Case.Events.DATA_SOURCE_ADDED,
|
||||
Case.Events.CONTENT_TAG_ADDED,
|
||||
Case.Events.CONTENT_TAG_DELETED
|
||||
Case.Events.CONTENT_TAG_DELETED,
|
||||
Case.Events.DATA_SOURCE_DELETED
|
||||
);
|
||||
|
||||
/*
|
||||
@ -802,6 +805,17 @@ public final class ImageGalleryController {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DATA_SOURCE_DELETED:
|
||||
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
final DataSourceDeletedEvent dataSourceDeletedEvent = (DataSourceDeletedEvent) event;
|
||||
long dataSourceObjId = dataSourceDeletedEvent.getDataSourceId();
|
||||
try {
|
||||
drawableDB.deleteDataSource(dataSourceObjId);
|
||||
} catch (SQLException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to delete data source (obj_id = %d)", dataSourceObjId), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_ADDED:
|
||||
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) event;
|
||||
long objId = tagAddedEvent.getAddedTag().getContent().getId();
|
||||
|
@ -100,15 +100,15 @@ public final class DrawableDB {
|
||||
private static final String GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS
|
||||
|
||||
private static final String IG_DB_INFO_TABLE = "image_gallery_db_info";
|
||||
|
||||
|
||||
private static final String IG_SCHEMA_MAJOR_VERSION_KEY = "IG_SCHEMA_MAJOR_VERSION";
|
||||
private static final String IG_SCHEMA_MINOR_VERSION_KEY = "IG_SCHEMA_MINOR_VERSION";
|
||||
private static final String IG_CREATION_SCHEMA_MAJOR_VERSION_KEY = "IG_CREATION_SCHEMA_MAJOR_VERSION";
|
||||
private static final String IG_CREATION_SCHEMA_MINOR_VERSION_KEY = "IG_CREATION_SCHEMA_MINOR_VERSION";
|
||||
|
||||
private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // IG Schema Starting version
|
||||
private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 1, 0); // IG Schema Current version
|
||||
|
||||
|
||||
private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // IG Schema Starting version - DO NOT CHANGE
|
||||
private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 2, 0); // IG Schema Current version
|
||||
|
||||
private PreparedStatement insertHashSetStmt;
|
||||
|
||||
private List<PreparedStatement> preparedStatements = new ArrayList<>();
|
||||
@ -146,6 +146,8 @@ public final class DrawableDB {
|
||||
|
||||
private PreparedStatement pathGroupFilterByDataSrcStmt;
|
||||
|
||||
private PreparedStatement deleteDataSourceStmt;
|
||||
|
||||
/**
|
||||
* map from {@link DrawableAttribute} to the {@link PreparedStatement} that
|
||||
* is used to select groups for that attribute
|
||||
@ -263,6 +265,7 @@ public final class DrawableDB {
|
||||
selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS
|
||||
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS
|
||||
removeHashHitStmt = prepareStatement("DELETE FROM hash_set_hits WHERE obj_id = ?"); //NON-NLS
|
||||
deleteDataSourceStmt = prepareStatement("DELETE FROM datasources where ds_obj_id = ?"); //NON-NLS
|
||||
return true;
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS
|
||||
@ -404,11 +407,12 @@ public final class DrawableDB {
|
||||
|
||||
/**
|
||||
* Checks if the specified table exists in Drawable DB
|
||||
*
|
||||
*
|
||||
* @param tableName table to check
|
||||
*
|
||||
* @return true if the table exists in the database
|
||||
*
|
||||
* @throws SQLException
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
private boolean doesTableExist(String tableName) throws SQLException {
|
||||
ResultSet tableQueryResults = null;
|
||||
@ -421,15 +425,14 @@ public final class DrawableDB {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
if (tableQueryResults != null) {
|
||||
tableQueryResults.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return tableExists;
|
||||
}
|
||||
|
||||
|
||||
private static void deleteDatabaseIfOlderVersion(Path dbPath) throws SQLException, IOException {
|
||||
if (Files.exists(dbPath)) {
|
||||
boolean hasDrawableFilesTable = false;
|
||||
@ -475,7 +478,7 @@ public final class DrawableDB {
|
||||
//allow to query while in transaction - no need read locks
|
||||
statement.execute("PRAGMA read_uncommitted = True;"); //NON-NLS
|
||||
|
||||
//TODO: do we need this?
|
||||
//used for data source deletion
|
||||
statement.execute("PRAGMA foreign_keys = ON"); //NON-NLS
|
||||
|
||||
//TODO: test this
|
||||
@ -515,7 +518,7 @@ public final class DrawableDB {
|
||||
dbWriteLock();
|
||||
try {
|
||||
boolean drawableDbTablesExist = true;
|
||||
|
||||
|
||||
if (isClosed()) {
|
||||
logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS
|
||||
return false;
|
||||
@ -532,31 +535,31 @@ public final class DrawableDB {
|
||||
* Create tables in the drawables database.
|
||||
*/
|
||||
try (Statement stmt = con.createStatement()) {
|
||||
|
||||
|
||||
// Check if the database is new or an existing database
|
||||
drawableDbTablesExist = doesTableExist("drawable_files");
|
||||
if (false == doesTableExist(IG_DB_INFO_TABLE)) {
|
||||
try {
|
||||
VersionNumber ig_creation_schema_version = drawableDbTablesExist
|
||||
? IG_STARTING_SCHEMA_VERSION
|
||||
: IG_SCHEMA_VERSION;
|
||||
|
||||
VersionNumber ig_creation_schema_version = drawableDbTablesExist
|
||||
? IG_STARTING_SCHEMA_VERSION
|
||||
: IG_SCHEMA_VERSION;
|
||||
|
||||
stmt.execute("CREATE TABLE IF NOT EXISTS " + IG_DB_INFO_TABLE + " (name TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
||||
|
||||
|
||||
// backfill creation schema ver
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor() ));
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor() ));
|
||||
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
|
||||
|
||||
// set current schema ver: at DB initialization - current version is same as starting version
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor() ));
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor() ));
|
||||
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
|
||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
|
||||
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create ig_db_info table", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS datasources " //NON-NLS
|
||||
+ "( id INTEGER PRIMARY KEY, " //NON-NLS
|
||||
@ -578,7 +581,8 @@ public final class DrawableDB {
|
||||
+ " modified_time integer, " //NON-NLS
|
||||
+ " make TEXT DEFAULT NULL, " //NON-NLS
|
||||
+ " model TEXT DEFAULT NULL, " //NON-NLS
|
||||
+ " analyzed integer DEFAULT 0)"; //NON-NLS
|
||||
+ " analyzed integer DEFAULT 0, " //NON-NLS
|
||||
+ " FOREIGN KEY (data_source_obj_id) REFERENCES datasources(ds_obj_id) ON DELETE CASCADE)"; //NON-NLS
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create drawable_files table", ex); //NON-NLS
|
||||
@ -598,8 +602,9 @@ public final class DrawableDB {
|
||||
try {
|
||||
String sql = "CREATE TABLE if not exists hash_set_hits " //NON-NLS
|
||||
+ "(hash_set_id INTEGER REFERENCES hash_sets(hash_set_id) not null, " //NON-NLS
|
||||
+ " obj_id BIGINT REFERENCES drawable_files(obj_id) not null, " //NON-NLS
|
||||
+ " PRIMARY KEY (hash_set_id, obj_id))"; //NON-NLS
|
||||
+ " obj_id BIGINT NOT NULL, " //NON-NLS
|
||||
+ " PRIMARY KEY (hash_set_id, obj_id), " //NON-NLS
|
||||
+ " FOREIGN KEY (obj_id) REFERENCES drawable_files(obj_id) ON DELETE CASCADE)"; //NON-NLS
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create hash_set_hits table", ex); //NON-NLS
|
||||
@ -649,45 +654,45 @@ public final class DrawableDB {
|
||||
* Create tables in the case database.
|
||||
*/
|
||||
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
|
||||
|
||||
|
||||
try {
|
||||
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
|
||||
VersionNumber ig_creation_schema_version = caseDbTablesExist
|
||||
? IG_STARTING_SCHEMA_VERSION
|
||||
: IG_SCHEMA_VERSION;
|
||||
|
||||
String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
|
||||
+ " name TEXT UNIQUE NOT NULL,"
|
||||
+ " value TEXT NOT NULL )";
|
||||
tskCase.getCaseDbAccessManager().createTable(IG_DB_INFO_TABLE, tableSchema);
|
||||
|
||||
// backfill creation version
|
||||
String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
||||
String creationMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
|
||||
|
||||
// set current version - at the onset, current version is same as creation version
|
||||
String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
||||
String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
|
||||
|
||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
||||
creationMajorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
|
||||
currentMajorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
}
|
||||
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL);
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL);
|
||||
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL);
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
|
||||
VersionNumber ig_creation_schema_version = caseDbTablesExist
|
||||
? IG_STARTING_SCHEMA_VERSION
|
||||
: IG_SCHEMA_VERSION;
|
||||
|
||||
String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
|
||||
+ " name TEXT UNIQUE NOT NULL,"
|
||||
+ " value TEXT NOT NULL )";
|
||||
tskCase.getCaseDbAccessManager().createTable(IG_DB_INFO_TABLE, tableSchema);
|
||||
|
||||
// backfill creation version
|
||||
String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
||||
String creationMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
|
||||
|
||||
// set current version - at the onset, current version is same as creation version
|
||||
String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
||||
String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
|
||||
|
||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
||||
creationMajorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
|
||||
currentMajorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||
}
|
||||
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL);
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL);
|
||||
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL);
|
||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
String tableSchema
|
||||
= "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS
|
||||
@ -710,7 +715,7 @@ public final class DrawableDB {
|
||||
+ " examiner_id integer not null, " //NON-NLS
|
||||
+ " seen integer DEFAULT 0, " //NON-NLS
|
||||
+ " UNIQUE(group_id, examiner_id),"
|
||||
+ " FOREIGN KEY(group_id) REFERENCES " + GROUPS_TABLENAME + "(group_id),"
|
||||
+ " FOREIGN KEY(group_id) REFERENCES " + GROUPS_TABLENAME + "(group_id) ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id)"
|
||||
+ " )"; //NON-NLS
|
||||
|
||||
@ -729,20 +734,21 @@ public final class DrawableDB {
|
||||
|
||||
/**
|
||||
* Gets the Schema version from DrawableDB
|
||||
*
|
||||
*
|
||||
* @return image gallery schema version in DrawableDB
|
||||
*
|
||||
* @throws SQLException
|
||||
* @throws TskCoreException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private VersionNumber getDrawableDbIgSchemaVersion() throws SQLException, TskCoreException {
|
||||
|
||||
|
||||
Statement statement = con.createStatement();
|
||||
ResultSet resultSet = null;
|
||||
|
||||
try {
|
||||
try {
|
||||
int majorVersion = -1;
|
||||
String majorVersionStr = null;
|
||||
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY));
|
||||
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY));
|
||||
if (resultSet.next()) {
|
||||
majorVersionStr = resultSet.getString("value");
|
||||
try {
|
||||
@ -751,12 +757,12 @@ public final class DrawableDB {
|
||||
throw new TskCoreException("Bad value for schema major version = " + majorVersionStr, ex);
|
||||
}
|
||||
} else {
|
||||
throw new TskCoreException("Failed to read schema major version from ig_db_info table");
|
||||
throw new TskCoreException("Failed to read schema major version from ig_db_info table");
|
||||
}
|
||||
|
||||
|
||||
int minorVersion = -1;
|
||||
String minorVersionStr = null;
|
||||
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY));
|
||||
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY));
|
||||
if (resultSet.next()) {
|
||||
minorVersionStr = resultSet.getString("value");
|
||||
try {
|
||||
@ -768,9 +774,8 @@ public final class DrawableDB {
|
||||
throw new TskCoreException("Failed to read schema minor version from ig_db_info table");
|
||||
}
|
||||
|
||||
return new VersionNumber(majorVersion, minorVersion, 0 );
|
||||
}
|
||||
finally {
|
||||
return new VersionNumber(majorVersion, minorVersion, 0);
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
resultSet.close();
|
||||
}
|
||||
@ -782,27 +787,28 @@ public final class DrawableDB {
|
||||
|
||||
/**
|
||||
* Gets the ImageGallery schema version from CaseDB
|
||||
*
|
||||
*
|
||||
* @return image gallery schema version in CaseDB
|
||||
*
|
||||
* @throws SQLException
|
||||
* @throws TskCoreException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private VersionNumber getCaseDbIgSchemaVersion() throws TskCoreException {
|
||||
|
||||
|
||||
// Callback to process result of get version query
|
||||
class GetSchemaVersionQueryResultProcessor implements CaseDbAccessQueryCallback {
|
||||
|
||||
private int version = -1;
|
||||
|
||||
int getVersion() {
|
||||
|
||||
int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void process(ResultSet resultSet) {
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
String versionStr = resultSet.getString("value");
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
String versionStr = resultSet.getString("value");
|
||||
try {
|
||||
version = Integer.parseInt(versionStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
@ -811,100 +817,96 @@ public final class DrawableDB {
|
||||
} else {
|
||||
logger.log(Level.SEVERE, "Failed to get version");
|
||||
}
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get version", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GetSchemaVersionQueryResultProcessor majorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
|
||||
GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
|
||||
|
||||
String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
|
||||
|
||||
String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
|
||||
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY), majorVersionResultProcessor);
|
||||
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor);
|
||||
|
||||
|
||||
return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the IG schema version in the Drawable DB
|
||||
*
|
||||
* @param version new version number
|
||||
*
|
||||
* @param version new version number
|
||||
* @param transaction transaction under which the update happens
|
||||
*
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
private void updateDrawableDbIgSchemaVersion(VersionNumber version, DrawableTransaction transaction) throws SQLException, TskCoreException {
|
||||
|
||||
|
||||
if (transaction == null) {
|
||||
throw new TskCoreException("Schema version update must be done in a transaction");
|
||||
throw new TskCoreException("Schema version update must be done in a transaction");
|
||||
}
|
||||
|
||||
|
||||
dbWriteLock();
|
||||
try {
|
||||
Statement statement = con.createStatement();
|
||||
|
||||
|
||||
// update schema version
|
||||
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY ));
|
||||
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY ));
|
||||
|
||||
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY));
|
||||
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY));
|
||||
|
||||
statement.close();
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
dbWriteUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the IG schema version in CaseDB
|
||||
*
|
||||
* @param version new version number
|
||||
*
|
||||
* @param version new version number
|
||||
* @param caseDbTransaction transaction to use to update the CaseDB
|
||||
*
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException {
|
||||
|
||||
|
||||
String updateSQLTemplate = " SET value = %s WHERE name = '%s' ";
|
||||
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY), caseDbTransaction);
|
||||
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction);
|
||||
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Upgrades the DB schema.
|
||||
*
|
||||
* @return true if the upgrade is successful
|
||||
*
|
||||
*
|
||||
* @throws SQLException
|
||||
*
|
||||
*
|
||||
*/
|
||||
private boolean upgradeDBSchema() throws TskCoreException, SQLException {
|
||||
|
||||
|
||||
// Read current version from the DBs
|
||||
VersionNumber drawableDbIgSchemaVersion = getDrawableDbIgSchemaVersion();
|
||||
VersionNumber drawableDbIgSchemaVersion = getDrawableDbIgSchemaVersion();
|
||||
VersionNumber caseDbIgSchemaVersion = getCaseDbIgSchemaVersion();
|
||||
|
||||
// Upgrade Schema in both DrawableDB and CaseDB
|
||||
CaseDbTransaction caseDbTransaction = tskCase.beginTransaction();
|
||||
DrawableTransaction transaction = beginTransaction();
|
||||
|
||||
|
||||
try {
|
||||
caseDbIgSchemaVersion = upgradeCaseDbIgSchema1dot0TO1dot1(caseDbIgSchemaVersion, caseDbTransaction);
|
||||
drawableDbIgSchemaVersion = upgradeDrawableDbIgSchema1dot0TO1dot1(drawableDbIgSchemaVersion, transaction);
|
||||
|
||||
// update the versions in the tables
|
||||
updateCaseDbIgSchemaVersion(caseDbIgSchemaVersion, caseDbTransaction );
|
||||
updateDrawableDbIgSchemaVersion(drawableDbIgSchemaVersion, transaction);
|
||||
|
||||
updateCaseDbIgSchemaVersion(caseDbIgSchemaVersion, caseDbTransaction);
|
||||
updateDrawableDbIgSchemaVersion(drawableDbIgSchemaVersion, transaction);
|
||||
|
||||
caseDbTransaction.commit();
|
||||
caseDbTransaction = null;
|
||||
commitTransaction(transaction, false);
|
||||
transaction = null;
|
||||
}
|
||||
catch (TskCoreException | SQLException ex) {
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
if (null != caseDbTransaction) {
|
||||
try {
|
||||
caseDbTransaction.rollback();
|
||||
@ -921,57 +923,59 @@ public final class DrawableDB {
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upgrades IG tables in CaseDB from 1.0 to 1.1
|
||||
* Does nothing if the incoming version is not 1.0
|
||||
*
|
||||
* @param currVersion version to upgrade from
|
||||
* Upgrades IG tables in CaseDB from 1.0 to 1.1 Does nothing if the incoming
|
||||
* version is not 1.0
|
||||
*
|
||||
* @param currVersion version to upgrade from
|
||||
* @param caseDbTransaction transaction to use for all updates
|
||||
*
|
||||
*
|
||||
* @return new version number
|
||||
* @throws TskCoreException
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction ) throws TskCoreException {
|
||||
|
||||
private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction) throws TskCoreException {
|
||||
|
||||
// Upgrade if current version is 1.0
|
||||
// or 1.1 - a bug in versioning alllowed some databases to be versioned as 1.1 without the actual corresponding upgrade. This allows such databases to be fixed, if needed.
|
||||
if (!(currVersion.getMajor() == 1 &&
|
||||
(currVersion.getMinor() == 0 || currVersion.getMinor() == 1))) {
|
||||
if (!(currVersion.getMajor() == 1
|
||||
&& (currVersion.getMinor() == 0 || currVersion.getMinor() == 1))) {
|
||||
return currVersion;
|
||||
}
|
||||
|
||||
|
||||
// Add a 'is_analyzed' column to groups table in CaseDB
|
||||
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
|
||||
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction )) {
|
||||
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction)) {
|
||||
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
|
||||
}
|
||||
return new VersionNumber(1,1,0);
|
||||
}
|
||||
return new VersionNumber(1, 1, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upgrades IG tables in DrawableDB from 1.0 to 1.1
|
||||
* Does nothing if the incoming version is not 1.0
|
||||
*
|
||||
* @param currVersion version to upgrade from
|
||||
* Upgrades IG tables in DrawableDB from 1.0 to 1.1 Does nothing if the
|
||||
* incoming version is not 1.0
|
||||
*
|
||||
* @param currVersion version to upgrade from
|
||||
* @param transaction transaction to use for all updates
|
||||
*
|
||||
*
|
||||
* @return new version number
|
||||
* @throws TskCoreException
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private VersionNumber upgradeDrawableDbIgSchema1dot0TO1dot1(VersionNumber currVersion, DrawableTransaction transaction ) throws TskCoreException {
|
||||
|
||||
if (currVersion.getMajor() != 1 ||
|
||||
currVersion.getMinor() != 0) {
|
||||
private VersionNumber upgradeDrawableDbIgSchema1dot0TO1dot1(VersionNumber currVersion, DrawableTransaction transaction) throws TskCoreException {
|
||||
|
||||
if (currVersion.getMajor() != 1
|
||||
|| currVersion.getMinor() != 0) {
|
||||
return currVersion;
|
||||
}
|
||||
|
||||
|
||||
// There are no changes in DrawableDB schema in 1.0 -> 1.1
|
||||
return new VersionNumber(1,1,0);
|
||||
return new VersionNumber(1, 1, 0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
/*
|
||||
@ -1143,10 +1147,10 @@ public final class DrawableDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Record in the DB that the group with the given key is seen
|
||||
* by given examiner id.
|
||||
* Record in the DB that the group with the given key is seen by given
|
||||
* examiner id.
|
||||
*
|
||||
* @param groupKey key identifying the group.
|
||||
* @param groupKey key identifying the group.
|
||||
* @param examinerID examiner id.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
@ -1154,16 +1158,16 @@ public final class DrawableDB {
|
||||
public void markGroupSeen(GroupKey<?> groupKey, long examinerID) throws TskCoreException {
|
||||
|
||||
/*
|
||||
* Check the groupSeenCache to see if the seen status for this group was set recently.
|
||||
* If recently set to seen, there's no need to update it
|
||||
* Check the groupSeenCache to see if the seen status for this group was
|
||||
* set recently. If recently set to seen, there's no need to update it
|
||||
*/
|
||||
Boolean cachedValue = groupSeenCache.getIfPresent(groupKey);
|
||||
if (cachedValue != null && cachedValue == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// query to find the group id from attribute/value
|
||||
String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME //NON-NLS
|
||||
String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME //NON-NLS
|
||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )", //NON-NLS
|
||||
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||
@ -1180,8 +1184,8 @@ public final class DrawableDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Record in the DB that given group is unseen.
|
||||
* The group is marked unseen for ALL examiners that have seen the group.
|
||||
* Record in the DB that given group is unseen. The group is marked unseen
|
||||
* for ALL examiners that have seen the group.
|
||||
*
|
||||
* @param groupKey key identifying the group.
|
||||
*
|
||||
@ -1190,20 +1194,20 @@ public final class DrawableDB {
|
||||
public void markGroupUnseen(GroupKey<?> groupKey) throws TskCoreException {
|
||||
|
||||
/*
|
||||
* Check the groupSeenCache to see if the seen status for this group was set recently.
|
||||
* If recently set to unseen, there's no need to update it
|
||||
* Check the groupSeenCache to see if the seen status for this group was
|
||||
* set recently. If recently set to unseen, there's no need to update it
|
||||
*/
|
||||
Boolean cachedValue = groupSeenCache.getIfPresent(groupKey);
|
||||
if (cachedValue != null && cachedValue == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
String updateSQL = String.format(" SET seen = 0 WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")" ); //NON-NLS
|
||||
|
||||
String updateSQL = String.format(" SET seen = 0 WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")"); //NON-NLS
|
||||
tskCase.getCaseDbAccessManager().update(GROUPS_SEEN_TABLENAME, updateSQL);
|
||||
|
||||
|
||||
groupSeenCache.put(groupKey, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the isAnalysed flag in the groups table for the given group to true.
|
||||
*
|
||||
@ -1213,7 +1217,6 @@ public final class DrawableDB {
|
||||
*/
|
||||
public void markGroupAnalyzed(GroupKey<?> groupKey) throws TskCoreException {
|
||||
|
||||
|
||||
String updateSQL = String.format(" SET is_analyzed = %d "
|
||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
|
||||
1,
|
||||
@ -1223,7 +1226,7 @@ public final class DrawableDB {
|
||||
|
||||
tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a file from the drawables databse.
|
||||
*
|
||||
@ -1254,11 +1257,11 @@ public final class DrawableDB {
|
||||
|
||||
/**
|
||||
* Updates the image file.
|
||||
*
|
||||
*
|
||||
* @param f file to update.
|
||||
*
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws SQLException
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void updateFile(DrawableFile f) throws TskCoreException, SQLException {
|
||||
DrawableTransaction trans = null;
|
||||
@ -1288,14 +1291,13 @@ public final class DrawableDB {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update an existing entry (or make a new one) into the DB that includes
|
||||
* group information. Called when a file has been analyzed or during a bulk
|
||||
* rebuild
|
||||
*
|
||||
* @param f file to update
|
||||
* @param tr
|
||||
* @param f file to update
|
||||
* @param tr
|
||||
* @param caseDbTransaction
|
||||
*/
|
||||
public void updateFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) {
|
||||
@ -1601,24 +1603,24 @@ public final class DrawableDB {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the build status for the given data source.
|
||||
* Will return UNKNOWN if the data source is not yet in the database.
|
||||
*
|
||||
* Get the build status for the given data source. Will return UNKNOWN if
|
||||
* the data source is not yet in the database.
|
||||
*
|
||||
* @param dataSourceId
|
||||
*
|
||||
*
|
||||
* @return The status of the data source or UKNOWN if it is not found.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public DrawableDbBuildStatusEnum getDataSourceDbBuildStatus(Long dataSourceId) throws TskCoreException {
|
||||
public DrawableDbBuildStatusEnum getDataSourceDbBuildStatus(Long dataSourceId) throws TskCoreException {
|
||||
Map<Long, DrawableDbBuildStatusEnum> statusMap = getDataSourceDbBuildStatus();
|
||||
if (statusMap.containsKey(dataSourceId) == false) {
|
||||
return DrawableDbBuildStatusEnum.UNKNOWN;
|
||||
}
|
||||
return statusMap.get(dataSourceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert/update given data source object id and it's DB rebuild status in
|
||||
@ -1687,47 +1689,50 @@ public final class DrawableDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given group is analyzed and ready to be viewed.
|
||||
*
|
||||
* Returns whether or not the given group is analyzed and ready to be
|
||||
* viewed.
|
||||
*
|
||||
* @param groupKey group key.
|
||||
*
|
||||
* @return true if the group is analyzed.
|
||||
*
|
||||
* @throws SQLException
|
||||
* @throws TskCoreException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public Boolean isGroupAnalyzed(GroupKey<?> groupKey) throws SQLException, TskCoreException {
|
||||
|
||||
|
||||
// Callback to process result of isAnalyzed query
|
||||
class IsGroupAnalyzedQueryResultProcessor implements CaseDbAccessQueryCallback {
|
||||
|
||||
private boolean isAnalyzed = false;
|
||||
|
||||
boolean getIsAnalyzed() {
|
||||
|
||||
boolean getIsAnalyzed() {
|
||||
return isAnalyzed;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void process(ResultSet resultSet) {
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
isAnalyzed = resultSet.getInt("is_analyzed") == 1 ? true: false;
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
isAnalyzed = resultSet.getInt("is_analyzed") == 1 ? true : false;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get group is_analyzed", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor();
|
||||
try {
|
||||
String groupAnalyzedQueryStmt = String.format("is_analyzed FROM " + GROUPS_TABLENAME
|
||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
|
||||
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
||||
|
||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
|
||||
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
||||
|
||||
tskCase.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
|
||||
return queryResultProcessor.getIsAnalyzed();
|
||||
} catch ( TskCoreException ex) {
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = String.format("Failed to get group is_analyzed for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
}
|
||||
@ -1833,7 +1838,7 @@ public final class DrawableDB {
|
||||
|
||||
// skip any null/blank values
|
||||
query.append("WHERE LENGTH(" + groupBy.attrName.toString() + ") > 0 ");
|
||||
|
||||
|
||||
if (dataSource != null) {
|
||||
query.append(" AND data_source_obj_id = ").append(dataSource.getId());
|
||||
}
|
||||
@ -1922,7 +1927,7 @@ public final class DrawableDB {
|
||||
return;
|
||||
}
|
||||
|
||||
int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
|
||||
int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
|
||||
String insertSQL = String.format(" (data_source_obj_id, value, attribute, is_analyzed) VALUES (%d, \'%s\', \'%s\', %d)",
|
||||
ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()), isAnalyzed);
|
||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
||||
@ -1995,7 +2000,6 @@ public final class DrawableDB {
|
||||
return countFilesWhere(" 1 ");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* delete the row with obj_id = id.
|
||||
*
|
||||
@ -2027,6 +2031,37 @@ public final class DrawableDB {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a cascading delete of a data source, starting from the
|
||||
* datasources table.
|
||||
*
|
||||
* @param dataSourceID The object ID of the data source to delete.
|
||||
*
|
||||
* @throws SQLException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public void deleteDataSource(long dataSourceID) throws SQLException, TskCoreException {
|
||||
dbWriteLock();
|
||||
DrawableTransaction trans = null;
|
||||
try {
|
||||
trans = beginTransaction();
|
||||
deleteDataSourceStmt.setLong(1, dataSourceID);
|
||||
deleteDataSourceStmt.executeUpdate();
|
||||
commitTransaction(trans, true);
|
||||
} catch (SQLException | TskCoreException ex) {
|
||||
if (null != trans) {
|
||||
try {
|
||||
rollbackTransaction(trans);
|
||||
} catch (SQLException ex2) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to roll back drawables db transaction after error: %s", ex.getMessage()), ex2); //NON-NLS
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
} finally {
|
||||
dbWriteUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
public class MultipleTransactionException extends IllegalStateException {
|
||||
|
||||
public MultipleTransactionException() {
|
||||
@ -2203,11 +2238,11 @@ public final class DrawableDB {
|
||||
|
||||
// The files are processed ORDERED BY parent path
|
||||
// We want to preserve that order here, so that we can detect a
|
||||
// change in path, and thus mark the path group as analyzed
|
||||
// Hence we use a LinkedHashSet here.
|
||||
// change in path, and thus mark the path group as analyzed
|
||||
// Hence we use a LinkedHashSet here.
|
||||
private final Set<Long> updatedFiles = new LinkedHashSet<>();
|
||||
private final Set<Long> removedFiles = new LinkedHashSet<>();
|
||||
|
||||
|
||||
private boolean completed;
|
||||
|
||||
private DrawableTransaction() throws TskCoreException, SQLException {
|
||||
|
@ -298,6 +298,7 @@ GlobalEditListPanel.editWordButton.text=Edit Keyword
|
||||
SolrSearchService.ServiceName=Solr Keyword Search Service
|
||||
SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only
|
||||
SolrSearchService.IndexReadOnlyDialog.msg=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html>
|
||||
SolrSearchService.DeleteDataSource.msg=Error Deleting Solr data for data source id {0}
|
||||
ExtractedContentPanel.jLabel1.text=Text Source:
|
||||
ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton
|
||||
ExtractedContentPanel.pagePreviousButton.text=
|
||||
|
@ -357,6 +357,7 @@ SolrSearchService.indexingError=Unable to index blackboard artifact.
|
||||
SolrSearchService.ServiceName=Solr Keyword Search Service
|
||||
SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only
|
||||
SolrSearchService.IndexReadOnlyDialog.msg=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html>
|
||||
SolrSearchService.DeleteDataSource.msg=Error Deleting Solr data for data source id {0}
|
||||
ExtractedContentPanel.jLabel1.text=Text Source:
|
||||
ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton
|
||||
ExtractedContentPanel.pagePreviousButton.text=
|
||||
|
@ -189,16 +189,16 @@ public class Server {
|
||||
}
|
||||
},
|
||||
/**
|
||||
* termfreq is a function which returns the number of times the term appears.
|
||||
* This is not an actual field defined in schema.xml, but can be gotten from returned documents
|
||||
* in the same way as fields.
|
||||
* termfreq is a function which returns the number of times the term
|
||||
* appears. This is not an actual field defined in schema.xml, but can
|
||||
* be gotten from returned documents in the same way as fields.
|
||||
*/
|
||||
TERMFREQ {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "termfreq"; //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static final String HL_ANALYZE_CHARS_UNLIMITED = "500000"; //max 1MB in a chunk. use -1 for unlimited, but -1 option may not be supported (not documented)
|
||||
@ -525,7 +525,7 @@ public class Server {
|
||||
@Override
|
||||
public void run() {
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(this.getClass(), "Installer.errorInitKsmMsg"),
|
||||
NbBundle.getMessage(this.getClass(), "Installer.errorInitKsmMsg"),
|
||||
Bundle.Server_status_failed_msg());
|
||||
}
|
||||
});
|
||||
@ -890,28 +890,30 @@ public class Server {
|
||||
throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the host and port for a multiuser case.
|
||||
* If the file solrserver.txt exists, then use the values from that file.
|
||||
* Otherwise use the settings from the properties file.
|
||||
*
|
||||
* Get the host and port for a multiuser case. If the file solrserver.txt
|
||||
* exists, then use the values from that file. Otherwise use the settings
|
||||
* from the properties file.
|
||||
*
|
||||
* @param caseDirectory Current case directory
|
||||
* @return IndexingServerProperties containing the solr host/port for this case
|
||||
*
|
||||
* @return IndexingServerProperties containing the solr host/port for this
|
||||
* case
|
||||
*/
|
||||
public static IndexingServerProperties getMultiUserServerProperties(String caseDirectory) {
|
||||
|
||||
Path serverFilePath = Paths.get(caseDirectory, "solrserver.txt");
|
||||
if(serverFilePath.toFile().exists()){
|
||||
try{
|
||||
if (serverFilePath.toFile().exists()) {
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(serverFilePath);
|
||||
if(lines.isEmpty()) {
|
||||
if (lines.isEmpty()) {
|
||||
logger.log(Level.SEVERE, "solrserver.txt file does not contain any data");
|
||||
} else if (! lines.get(0).contains(",")) {
|
||||
} else if (!lines.get(0).contains(",")) {
|
||||
logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
|
||||
} else {
|
||||
String[] parts = lines.get(0).split(",");
|
||||
if(parts.length != 2) {
|
||||
if (parts.length != 2) {
|
||||
logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
|
||||
} else {
|
||||
return new IndexingServerProperties(parts[0], parts[1]);
|
||||
@ -921,102 +923,104 @@ public class Server {
|
||||
logger.log(Level.SEVERE, "solrserver.txt file could not be read", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Default back to the user preferences if the solrserver.txt file was not found or if an error occurred
|
||||
String host = UserPreferences.getIndexingServerHost();
|
||||
String port = UserPreferences.getIndexingServerPort();
|
||||
return new IndexingServerProperties(host, port);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a solr server to use for this case and record it in the case directory.
|
||||
* Looks for a file named "solrServerList.txt" in the root output directory -
|
||||
* if this does not exist then no server is recorded.
|
||||
*
|
||||
* Format of solrServerList.txt:
|
||||
* (host),(port)
|
||||
* Ex: 10.1.2.34,8983
|
||||
*
|
||||
* Pick a solr server to use for this case and record it in the case
|
||||
* directory. Looks for a file named "solrServerList.txt" in the root output
|
||||
* directory - if this does not exist then no server is recorded.
|
||||
*
|
||||
* Format of solrServerList.txt: (host),(port) Ex: 10.1.2.34,8983
|
||||
*
|
||||
* @param rootOutputDirectory
|
||||
* @param caseDirectoryPath
|
||||
* @throws KeywordSearchModuleException
|
||||
*
|
||||
* @throws KeywordSearchModuleException
|
||||
*/
|
||||
public static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath) throws KeywordSearchModuleException {
|
||||
// Look for the solr server list file
|
||||
String serverListName = "solrServerList.txt";
|
||||
Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName);
|
||||
if(serverListPath.toFile().exists()){
|
||||
|
||||
if (serverListPath.toFile().exists()) {
|
||||
|
||||
// Read the list of solr servers
|
||||
List<String> lines;
|
||||
try{
|
||||
try {
|
||||
lines = Files.readAllLines(serverListPath);
|
||||
} catch (IOException ex){
|
||||
} catch (IOException ex) {
|
||||
throw new KeywordSearchModuleException(serverListName + " could not be read", ex);
|
||||
}
|
||||
|
||||
|
||||
// Remove any lines that don't contain a comma (these are likely just whitespace)
|
||||
for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) {
|
||||
String line = iterator.next();
|
||||
if (! line.contains(",")) {
|
||||
if (!line.contains(",")) {
|
||||
// Remove the current element from the iterator and the list.
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
if(lines.isEmpty()) {
|
||||
if (lines.isEmpty()) {
|
||||
throw new KeywordSearchModuleException(serverListName + " had no valid server information");
|
||||
}
|
||||
|
||||
|
||||
// Choose which server to use
|
||||
int rnd = new Random().nextInt(lines.size());
|
||||
String[] parts = lines.get(rnd).split(",");
|
||||
if(parts.length != 2) {
|
||||
if (parts.length != 2) {
|
||||
throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd));
|
||||
}
|
||||
|
||||
|
||||
// Split it up just to do a sanity check on the data
|
||||
String host = parts[0];
|
||||
String port = parts[1];
|
||||
if(host.isEmpty() || port.isEmpty()) {
|
||||
String port = parts[1];
|
||||
if (host.isEmpty() || port.isEmpty()) {
|
||||
throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd));
|
||||
}
|
||||
|
||||
|
||||
// Write the server data to a file
|
||||
Path serverFile = Paths.get(caseDirectoryPath.toString(), "solrserver.txt");
|
||||
try {
|
||||
caseDirectoryPath.toFile().mkdirs();
|
||||
if (! caseDirectoryPath.toFile().exists()) {
|
||||
if (!caseDirectoryPath.toFile().exists()) {
|
||||
throw new KeywordSearchModuleException("Case directory " + caseDirectoryPath.toString() + " does not exist");
|
||||
}
|
||||
Files.write(serverFile, lines.get(rnd).getBytes());
|
||||
} catch (IOException ex){
|
||||
} catch (IOException ex) {
|
||||
throw new KeywordSearchModuleException(serverFile.toString() + " could not be written", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to store the current server properties
|
||||
*/
|
||||
public static class IndexingServerProperties {
|
||||
|
||||
private final String host;
|
||||
private final String port;
|
||||
|
||||
IndexingServerProperties (String host, String port) {
|
||||
|
||||
IndexingServerProperties(String host, String port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host
|
||||
*
|
||||
* @return host
|
||||
*/
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the port
|
||||
*
|
||||
* @return port
|
||||
*/
|
||||
public String getPort() {
|
||||
@ -1265,6 +1269,26 @@ public class Server {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a data source from SOLR.
|
||||
*
|
||||
* @param dataSourceId to delete
|
||||
*
|
||||
* @throws NoOpenCoreException
|
||||
*/
|
||||
void deleteDataSource(Long dataSourceId) throws IOException, KeywordSearchModuleException, NoOpenCoreException, SolrServerException {
|
||||
try {
|
||||
currentCoreLock.writeLock().lock();
|
||||
if (null == currentCore) {
|
||||
throw new NoOpenCoreException();
|
||||
}
|
||||
currentCore.deleteDataSource(dataSourceId);
|
||||
currentCore.commit();
|
||||
} finally {
|
||||
currentCoreLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text contents of the given file as stored in SOLR.
|
||||
*
|
||||
@ -1375,10 +1399,10 @@ public class Server {
|
||||
* @throws IOException
|
||||
*/
|
||||
void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException {
|
||||
TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
|
||||
TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
|
||||
CoreAdminRequest statusRequest = new CoreAdminRequest();
|
||||
statusRequest.setCoreName( null );
|
||||
statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS );
|
||||
statusRequest.setCoreName(null);
|
||||
statusRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
|
||||
statusRequest.setIndexInfoNeeded(false);
|
||||
statusRequest.process(solrServer);
|
||||
HealthMonitor.submitTimingMetric(metric);
|
||||
@ -1435,7 +1459,7 @@ public class Server {
|
||||
// the server to access a core needs to be built from a URL with the
|
||||
// core in it, and is only good for core-specific operations
|
||||
private final HttpSolrServer solrCore;
|
||||
|
||||
|
||||
private final int QUERY_TIMEOUT_MILLISECONDS = 86400000; // 24 Hours = 86,400,000 Milliseconds
|
||||
|
||||
private Core(String name, CaseType caseType, Index index) {
|
||||
@ -1447,7 +1471,7 @@ public class Server {
|
||||
|
||||
//TODO test these settings
|
||||
// socket read timeout, make large enough so can index larger files
|
||||
solrCore.setSoTimeout(QUERY_TIMEOUT_MILLISECONDS);
|
||||
solrCore.setSoTimeout(QUERY_TIMEOUT_MILLISECONDS);
|
||||
//solrCore.setConnectionTimeout(1000);
|
||||
solrCore.setDefaultMaxConnectionsPerHost(32);
|
||||
solrCore.setMaxTotalConnections(32);
|
||||
@ -1506,6 +1530,13 @@ public class Server {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteDataSource(Long dsObjId) throws IOException, SolrServerException {
|
||||
String dataSourceId = Long.toString(dsObjId);
|
||||
String deleteQuery = "image_id:" + dataSourceId;
|
||||
|
||||
solrCore.deleteByQuery(deleteQuery);
|
||||
}
|
||||
|
||||
void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException {
|
||||
try {
|
||||
solrCore.add(doc);
|
||||
@ -1527,7 +1558,8 @@ public class Server {
|
||||
* @param chunkID Chunk ID of the Solr document
|
||||
*
|
||||
* @return Text from matching Solr document (as String). Null if no
|
||||
* matching Solr document found or error while getting content from Solr
|
||||
* matching Solr document found or error while getting content
|
||||
* from Solr
|
||||
*/
|
||||
private String getSolrContent(long contentID, int chunkID) {
|
||||
final SolrQuery q = new SolrQuery();
|
||||
|
@ -60,8 +60,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* text indexing and search.
|
||||
*/
|
||||
@ServiceProviders(value = {
|
||||
@ServiceProvider(service = KeywordSearchService.class)
|
||||
,
|
||||
@ServiceProvider(service = KeywordSearchService.class),
|
||||
@ServiceProvider(service = AutopsyService.class)
|
||||
})
|
||||
public class SolrSearchService implements KeywordSearchService, AutopsyService {
|
||||
@ -194,6 +193,26 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a data source from Solr for a case.
|
||||
*
|
||||
* @param dataSourceId the id of the data source to delete.
|
||||
*
|
||||
* @throws
|
||||
* org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException
|
||||
*/
|
||||
@Override
|
||||
public void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException {
|
||||
|
||||
try {
|
||||
Server ddsServer = KeywordSearch.getServer();
|
||||
ddsServer.deleteDataSource(dataSourceId);
|
||||
} catch (IOException | KeywordSearchModuleException | NoOpenCoreException | SolrServerException ex) {
|
||||
logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
|
||||
throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes Solr core for a case.
|
||||
*
|
||||
@ -275,7 +294,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
|
||||
String caseDirPath = context.getCase().getCaseDirectory();
|
||||
Case theCase = context.getCase();
|
||||
List<Index> indexes = new ArrayList<>();
|
||||
progress.start(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
|
||||
progress.progress(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
|
||||
if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
|
||||
try {
|
||||
// metadata file exists, get list of existing Solr cores for this case
|
||||
|
@ -1,5 +1,5 @@
|
||||
#Updated by build script
|
||||
#Fri, 04 Oct 2019 14:30:10 -0400
|
||||
#Tue, 12 Nov 2019 17:21:46 -0500
|
||||
LBL_splash_window_title=Starting Autopsy
|
||||
SPLASH_HEIGHT=314
|
||||
SPLASH_WIDTH=538
|
||||
|
@ -1,4 +1,4 @@
|
||||
#Updated by build script
|
||||
#Fri, 04 Oct 2019 14:30:10 -0400
|
||||
#Tue, 12 Nov 2019 17:21:46 -0500
|
||||
CTL_MainWindow_Title=Autopsy 4.13.0
|
||||
CTL_MainWindow_Title_No_Project=Autopsy 4.13.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user