/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 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.Cursor;
import java.awt.Frame;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
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.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.services.Services;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl;
import org.sleuthkit.autopsy.coreutils.DriveUtils;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
/**
* An Autopsy case. Currently, only one case at a time may be open.
*/
public class Case implements SleuthkitCase.ErrorObserver {
/**
* An enumeration of case types.
*/
@NbBundle.Messages({"Case_caseType_singleUser=Single-user case", "Case_caseType_multiUser=Multi-user case"})
public enum CaseType {
SINGLE_USER_CASE("Single-user case"), //NON-NLS
MULTI_USER_CASE("Multi-user case"); //NON-NLS
private final String typeName;
/**
* Constructs a case type.
*
* @param typeName The type name.
*/
private CaseType(String typeName) {
this.typeName = typeName;
}
/**
* Gets the string representation of this case type.
*
* @return
*/
@Override
public String toString() {
return typeName;
}
/**
* Gets the localized display name for this case type.
*
* @return The dis[play name.
*/
String getLocalizedDisplayName() {
if (fromString(typeName) == SINGLE_USER_CASE) {
return Bundle.Case_caseType_singleUser();
} else {
return Bundle.Case_caseType_multiUser();
}
}
/**
* Gets a case type from a case type name string
*
* @param typeName The case type name string.
*
* @return
*/
public static CaseType fromString(String typeName) {
if (typeName != null) {
for (CaseType c : CaseType.values()) {
if (typeName.equalsIgnoreCase(c.typeName)) {
return c;
}
}
}
return null;
}
/**
* Tests the equality of the type name of this case type with a case
* type name.
*
* @param otherTypeName A case type name
*
* @return True or false
*
* @deprecated Do not use.
*/
@Deprecated
public boolean equalsName(String otherTypeName) {
return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
}
};
/**
* An enumeration of events (property change events) a case may publish
* (fire).
*/
public enum Events {
/**
* The name of the current case has changed. The old value of the
* PropertyChangeEvent is the old case name (type: String), the new
* value is the new case name (type: String).
*/
NAME,
/**
* The number of the current case has changed. The old value of the
* PropertyChangeEvent is the old number (type: String), the new value
* is the new case number (type: String).
*/
NUMBER,
/**
* The examiner associated with the current case has changed. The old
* value of the PropertyChangeEvent is the old examiner (type: String),
* the new value is the new examiner (type: String).
*/
EXAMINER,
/**
* An attempt to add a new data source to the current case is being
* made. The old and new values of the PropertyChangeEvent are null.
* Cast the PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent to
* access event data.
*/
ADDING_DATA_SOURCE,
/**
* A failure to add a new data source to the current case has occurred.
* The old and new values of the PropertyChangeEvent are null. Cast the
* PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent
* to access event data.
*/
ADDING_DATA_SOURCE_FAILED,
/**
* A new data source has been added to the current case. The old value
* of the PropertyChangeEvent is null, the new value is the newly-added
* data source (type: Content). Cast the PropertyChangeEvent to
* org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent to
* access event data.
*/
DATA_SOURCE_ADDED,
/**
* A data source has been deleted from the current case. The old value
* of the PropertyChangeEvent is the object id of the data source that
* was deleted (type: Long), the new value is null.
*/
DATA_SOURCE_DELETED,
/**
* The current case has changed. If a case has been opened, the old
* value of the PropertyChangeEvent is null, the new value is the new
* case (type: Case). If a case has been closed, the old value of the
* PropertyChangeEvent is the closed case (type: Case), the new value is
* null.
*/
CURRENT_CASE,
/**
* A report has been added to the current case. The old value of the
* PropertyChangeEvent is null, the new value is the report (type:
* Report).
*/
REPORT_ADDED,
/**
* A report has been added to the current case. The old value of the
* PropertyChangeEvent is the report (type: Report), the new value is
* null.
*/
REPORT_DELETED,
/**
* An artifact associated with the current case has been tagged. The old
* value of the PropertyChangeEvent is null, the new value is the tag
* (type: BlackBoardArtifactTag).
*/
BLACKBOARD_ARTIFACT_TAG_ADDED,
/**
* A tag has been removed from an artifact associated with the current
* case. The old value of the PropertyChangeEvent is is the tag info
* (type:
* BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo),
* the new value is null.
*/
BLACKBOARD_ARTIFACT_TAG_DELETED,
/**
* Content associated with the current case has been tagged. The old
* value of the PropertyChangeEvent is null, the new value is the tag
* (type: ContentTag).
*/
CONTENT_TAG_ADDED,
/**
* A tag has been removed from content associated with the current case.
* The old value of the PropertyChangeEvent is is the tag info (type:
* ContentTagDeletedEvent.DeletedContentTagInfo), the new value is null.
*/
CONTENT_TAG_DELETED;
};
private static final int MAX_SANITIZED_CASE_NAME_LEN = 47;
private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
private static final String CACHE_FOLDER = "Cache"; //NON-NLS
private static final String EXPORT_FOLDER = "Export"; //NON-NLS
private static final String LOG_FOLDER = "Log"; //NON-NLS
private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
private static final String TEMP_FOLDER = "Temp"; //NON-NLS
static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60;
private static final Logger logger = Logger.getLogger(Case.class.getName());
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
private static String appName;
private static Case currentCase;
private final CaseMetadata caseMetadata;
private final SleuthkitCase db;
private final Services services;
private CollaborationMonitor collaborationMonitor;
private boolean hasDataSources;
private volatile IntervalErrorReportData tskErrorReporter;
/**
* Constructs an Autopsy case. Currently, only one case at a time may be
* open.
*/
private Case(CaseMetadata caseMetadata, SleuthkitCase db) {
this.caseMetadata = caseMetadata;
this.db = db;
this.services = new Services(db);
}
/**
* Adds a subscriber to all case events. To subscribe to only specific
* events, use one of the overloads of addEventSubscriber.
*
* @param listener The subscriber (PropertyChangeListener) to add.
*/
public static void addPropertyChangeListener(PropertyChangeListener listener) {
addEventSubscriber(Stream.of(Events.values())
.map(Events::toString)
.collect(Collectors.toSet()), listener);
}
/**
* Removes a subscriber to all case events. To remove a subscription to only
* specific events, use one of the overloads of removeEventSubscriber.
*
* @param listener The subscriber (PropertyChangeListener) to remove.
*/
public static void removePropertyChangeListener(PropertyChangeListener listener) {
removeEventSubscriber(Stream.of(Events.values())
.map(Events::toString)
.collect(Collectors.toSet()), listener);
}
/**
* Adds a subscriber to specific case events.
*
* @param eventNames The events the subscriber is interested in.
* @param subscriber The subscriber (PropertyChangeListener) to add.
*/
public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(eventNames, subscriber);
}
/**
* Adds a subscriber to specific case events.
*
* @param eventName The event the subscriber is interested in.
* @param subscriber The subscriber (PropertyChangeListener) to add.
*/
public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
eventPublisher.addSubscriber(eventName, subscriber);
}
/**
* Removes a subscriber to specific case events.
*
* @param eventName The event the subscriber is no longer interested in.
* @param subscriber The subscriber (PropertyChangeListener) to remove.
*/
public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(eventName, subscriber);
}
/**
* Removes a subscriber to specific case events.
*
* @param eventNames The event the subscriber is no longer interested in.
* @param subscriber The subscriber (PropertyChangeListener) to remove.
*/
public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
eventPublisher.removeSubscriber(eventNames, subscriber);
}
/**
* Checks if case is currently open.
*
* @return True or false.
*/
public static boolean isCaseOpen() {
return currentCase != null;
}
/**
* Gets the current case, if there is one.
*
* @return The current case.
*
* @throws IllegalStateException if there is no current case.
*/
public static Case getCurrentCase() {
if (currentCase != null) {
return currentCase;
} else {
throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
}
}
/**
* Gets the case database.
*
* @return The case database.
*/
public SleuthkitCase getSleuthkitCase() {
return this.db;
}
/**
* Gets the case services manager.
*
* @return The case services manager.
*/
public Services getServices() {
return services;
}
/**
* Gets the case metadata.
*
* @return A CaseMetaData object.
*/
CaseMetadata getCaseMetadata() {
return caseMetadata;
}
/**
* Gets the case type.
*
* @return The case type.
*/
public CaseType getCaseType() {
return getCaseMetadata().getCaseType();
}
/**
* Gets the case create date.
*
* @return case The case create date.
*/
public String getCreatedDate() {
return getCaseMetadata().getCreatedDate();
}
/**
* Gets the case name.
*
* @return The case name.
*/
public String getName() {
return getCaseMetadata().getCaseName();
}
/**
* Updates the case name.
*
* This should not be called from the EDT.
*
* @param oldCaseName The old case name.
* @param oldPath The old path name.
* @param newCaseName The new case name.
* @param newPath The new case path.
*/
void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
try {
caseMetadata.setCaseName(newCaseName);
eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName));
SwingUtilities.invokeLater(() -> {
try {
RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case
addCaseNameToMainWindowTitle(newCaseName);
} catch (Exception ex) {
Logger.getLogger(Case.class.getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
}
});
} catch (Exception ex) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex);
}
}
/**
* Gets the case number.
*
* @return The case number
*/
public String getNumber() {
return caseMetadata.getCaseNumber();
}
/**
* Gets the examiner name.
*
* @return The examiner name.
*/
public String getExaminer() {
return caseMetadata.getExaminer();
}
/**
* Gets the path to the top-level case directory.
*
* @return The top-level case directory path.
*/
public String getCaseDirectory() {
return caseMetadata.getCaseDirectory();
}
/**
* Gets the root case output directory for this case, creating it if it does
* not exist. If the case is a single-user case, this is the case directory.
* If the case is a multi-user case, this is a subdirectory of the case
* directory specific to the host machine.
*
* @return the path to the host output directory.
*/
public String getOutputDirectory() {
String caseDirectory = getCaseDirectory();
Path hostPath;
if (getCaseMetadata().getCaseType() == CaseType.MULTI_USER_CASE) {
hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
} else {
hostPath = Paths.get(caseDirectory);
}
if (!hostPath.toFile().exists()) {
hostPath.toFile().mkdirs();
}
return hostPath.toString();
}
/**
* Gets the full path to the temp directory for this case, creating it if it
* does not exist.
*
* @return The temp subdirectory path.
*/
public String getTempDirectory() {
return getOrCreateSubdirectory(TEMP_FOLDER);
}
/**
* Gets the full path to the cache directory for this case, creating it if
* it does not exist.
*
* @return The cache directory path.
*/
public String getCacheDirectory() {
return getOrCreateSubdirectory(CACHE_FOLDER);
}
/**
* Gets the full path to the export directory for this case, creating it if
* it does not exist.
*
* @return The export directory path.
*/
public String getExportDirectory() {
return getOrCreateSubdirectory(EXPORT_FOLDER);
}
/**
* Gets the full path to the log directory for this case, creating it if it
* does not exist.
*
* @return The log directory path.
*/
public String getLogDirectoryPath() {
return getOrCreateSubdirectory(LOG_FOLDER);
}
/**
* Gets the full path to the reports directory for this case, creating it if
* it does not exist.
*
* @return The report directory path.
*/
public String getReportDirectory() {
return getOrCreateSubdirectory(REPORTS_FOLDER);
}
/**
* Gets the full path to the module output directory for this case, creating
* it if it does not exist.
*
* @return The module output directory path.
*/
public String getModuleDirectory() {
return getOrCreateSubdirectory(MODULE_FOLDER);
}
/**
* Gets the path of the module output directory for this case, relative to
* the case directory, creating it if it does not exist.
*
* @return The path to the module output directory, relative to the case
* directory.
*/
public String getModuleOutputDirectoryRelativePath() {
Path path = Paths.get(getModuleDirectory());
if (getCaseType() == CaseType.MULTI_USER_CASE) {
return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
} else {
return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
}
}
/**
* Gets the path to the specified subdirectory of the case directory,
* creating it if it does not already exist.
*
* @return The absolute path to the specified subdirectory.
*/
private String getOrCreateSubdirectory(String subDirectoryName) {
File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
if (!subDirectory.exists()) {
subDirectory.mkdirs();
}
return subDirectory.toString();
}
/**
* Gets the data sources for the case.
*
* @return A list of data sources.
*
* @throws org.sleuthkit.datamodel.TskCoreException if there is a problem
* querying the case
* database.
*/
public List<Content> getDataSources() throws TskCoreException {
List<Content> list = db.getRootObjects();
hasDataSources = (list.size() > 0);
return list;
}
/**
* Gets the time zone(s) of the image data source(s) in this case.
*
* @return The set of time zones in use.
*/
public Set<TimeZone> getTimeZones() {
Set<TimeZone> timezones = new HashSet<>();
try {
for (Content c : getDataSources()) {
final Content dataSource = c.getDataSource();
if ((dataSource != null) && (dataSource instanceof Image)) {
Image image = (Image) dataSource;
timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
}
return timezones;
}
/**
* Gets the name of the keyword search index for the case.
*
* @return The index name.
*/
public String getTextIndexName() {
return getCaseMetadata().getTextIndexName();
}
/**
* Queries whether or not the case has data, i.e., whether or not at least
* one data source has been added to the 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
}
}
return hasDataSources;
}
/**
* Notifies case event subscribers that a data source is being added to the
* case.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param eventId A unique identifier for the event. This UUID must be used
* to call notifyFailedAddingDataSource or
* notifyNewDataSource after the data source is added.
*/
public void notifyAddingDataSource(UUID eventId) {
eventPublisher.publish(new AddingDataSourceEvent(eventId));
}
/**
* Notifies case event subscribers that a data source failed to be added to
* the case.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param addingDataSourceEventId The unique identifier for the
* corresponding adding data source event
* (see notifyAddingDataSource).
*/
public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
}
/**
* Notifies case event subscribers that a data source has been added to the
* case database.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param dataSource The data source.
* @param addingDataSourceEventId The unique identifier for the
* corresponding adding data source event
* (see notifyAddingDataSource).
*/
public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
}
/**
* Notifies case event subscribers that a content tag has been added.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param newTag new ContentTag added
*/
public void notifyContentTagAdded(ContentTag newTag) {
eventPublisher.publish(new ContentTagAddedEvent(newTag));
}
/**
* Notifies case event subscribers that a content tag has been deleted.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param deletedTag ContentTag deleted
*/
public void notifyContentTagDeleted(ContentTag deletedTag) {
eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
}
/**
* Notifies case event subscribers that an artifact tag has been added.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param newTag new BlackboardArtifactTag added
*/
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
}
/**
* Notifies case event subscribers that an artifact tag has been deleted.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param deletedTag BlackboardArtifactTag deleted
*/
public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
}
/**
* Adds a report to the case.
*
* @param localPath The path of the report file, must be in the case
* directory or one of its subdirectories.
* @param srcModuleName The name of the module that created the report.
* @param reportName The report name, may be empty.
*
* @throws TskCoreException if there is a problem adding the report to the
* case database.
*/
public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
String normalizedLocalPath;
try {
normalizedLocalPath = Paths.get(localPath).normalize().toString();
} catch (InvalidPathException ex) {
String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
throw new TskCoreException(errorMsg, ex);
}
Report report = this.db.addReport(normalizedLocalPath, srcModuleName, reportName);
eventPublisher.publish(new ReportAddedEvent(report));
}
/**
* Gets the reports that have been added to the case.
*
* @return A collection of report objects.
*
* @throws TskCoreException if there is a problem querying the case
* database.
*/
public List<Report> getAllReports() throws TskCoreException {
return this.db.getAllReports();
}
/**
* Deletes one or more reports from the case database. Does not delete the
* report files.
*
* @param reports The report(s) to be deleted from the case.
*
* @throws TskCoreException if there is an error deleting the report(s).
*/
public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
for (Report report : reports) {
this.db.deleteReport(report);
eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
}
}
/**
* Allows the case database to report internal error conditions in
* situations where throwing an exception is not appropriate.
*
* @param context The context of the error condition.
* @param errorMessage An error message.
*/
@Override
public void receiveError(String context, String errorMessage) {
/*
* NOTE: We are accessing tskErrorReporter from two different threads.
* This is ok as long as we only read the value of tskErrorReporter
* because tskErrorReporter is declared as volatile.
*/
if (null != tskErrorReporter) {
tskErrorReporter.addProblems(context, errorMessage);
}
}
/**
* Closes this Autopsy case.
*
* @throws CaseActionException if there is a problem closing the case. The
* exception will have a user-friendly message
* and may be a wrapper for a lower-level
* exception.
*/
public void closeCase() throws CaseActionException {
changeCurrentCase(null);
try {
services.close();
this.db.close();
} catch (Exception e) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.closeCase.exception.msg"), e);
}
}
/**
* Deletes the case folder for this Autopsy case and sets the current case
* to null. It does not not delete the case database for a multi-user case.
*
* @param caseDir The case directory to delete.
*
* @throws CaseActionException exception throw if case could not be deleted
*/
void deleteCase(File caseDir) throws CaseActionException {
logger.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir); //NON-NLS
try {
boolean result = deleteCaseDirectory(caseDir);
RecentCases.getInstance().removeRecentCase(this.caseMetadata.getCaseName(), this.caseMetadata.getFilePath().toString()); // remove it from the recent case
Case.changeCurrentCase(null);
if (result == false) {
throw new CaseActionException(
NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg", caseDir));
}
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex); //NON-NLS
throw new CaseActionException(
NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg2", caseDir), ex);
}
}
/**
* Gets the application name.
*
* @return The application name.
*/
public static String getAppName() {
if ((appName == null) || appName.equals("")) {
appName = WindowManager.getDefault().getMainWindow().getTitle();
}
return appName;
}
/**
* Checks if a string is a valid case name.
*
* TODO( AUT-2221): This should incorporate the vlaidity checks of
* sanitizeCaseName.
*
* @param caseName The candidate string.
*
* @return True or false.
*/
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("|"));
}
/**
* Creates a new single-user Autopsy case.
*
* @param caseDir The full path of the case directory. It will be created
* if it doesn't already exist; if it exists, it should
* have been created using Case.createCaseDirectory to
* ensure that the required sub-directories were created.
* @param caseName The name of case.
* @param caseNumber The case number, can be the empty string.
* @param examiner The examiner to associate with the case, can be the
* empty string.
*
* @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.
*/
public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
}
/**
* Creates a new Autopsy case.
*
* @param caseDir The full path of the case directory. It will be created
* if it doesn't already exist; if it exists, it should
* have been created using Case.createCaseDirectory() to
* ensure that the required sub-directories were created.
* @param caseName The name of case.
* @param caseNumber The case number, can be the empty string.
* @param examiner The examiner to associate with the case, can be the
* empty string.
* @param caseType The type of case (single-user or multi-user).
*
* @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.
*/
@Messages({"Case.creationException=Could not create case: failed to create case metadata file."})
public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
logger.log(Level.INFO, "Attempting to create case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS
/*
* Create case directory if it doesn't already exist.
*/
if (new File(caseDir).exists() == false) {
Case.createCaseDirectory(caseDir, caseType);
}
/*
* Sanitize the case name, create a unique keyword search index name,
* and create a standard (single-user) or unique (multi-user) case
* database name.
*/
String santizedCaseName = sanitizeCaseName(caseName);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
Date date = new Date();
String indexName = santizedCaseName + "_" + dateFormat.format(date);
String dbName = null;
if (caseType == CaseType.SINGLE_USER_CASE) {
dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS
} else if (caseType == CaseType.MULTI_USER_CASE) {
dbName = indexName;
}
/*
* Create the case metadata (.aut) file.
*/
CaseMetadata metadata;
try {
metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName);
} catch (CaseMetadataException ex) {
throw new CaseActionException(Bundle.Case_creationException(), ex);
}
/*
* Create the case database.
*/
SleuthkitCase db = null;
try {
if (caseType == CaseType.SINGLE_USER_CASE) {
db = SleuthkitCase.newCase(dbName);
} else if (caseType == CaseType.MULTI_USER_CASE) {
db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir);
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
/*
* SleuthkitCase.newCase throws TskCoreExceptions with user-friendly
* messages, so propagate the exception message.
*/
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
} catch (UserPreferencesException ex) {
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
}
Case newCase = new Case(metadata, db);
changeCurrentCase(newCase);
logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS
}
/**
* Sanitizes the case name for PostgreSQL database, Solr cores, and ActiveMQ
* topics. Makes it plain-vanilla enough that each item should be able to
* use it.
*
* Solr:
* http://stackoverflow.com/questions/29977519/what-makes-an-invalid-core-name
* may not be / \ :
*
* ActiveMQ:
* http://activemq.2283324.n4.nabble.com/What-are-limitations-restrictions-on-destination-name-td4664141.html
* may not be ?
*
* PostgreSQL:
* http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63
* chars max, must start with a-z or _ following chars can be letters _ or
* digits
*
* SQLite: Uses autopsy.db for the database name and follows the Windows
* naming convention
*
* @param caseName A candidate case name.
*
* @return The sanitized case name.
*/
static String sanitizeCaseName(String caseName) {
String result;
// Remove all non-ASCII characters
result = caseName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
// Remove all control characters
result = result.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
// Remove / \ : ? space ' "
result = result.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
// Make it all lowercase
result = result.toLowerCase();
// Must start with letter or underscore for PostgreSQL. If not, prepend an underscore.
if (result.length() > 0 && !(Character.isLetter(result.codePointAt(0))) && !(result.codePointAt(0) == '_')) {
result = "_" + result;
}
// Chop to 63-16=47 left (63 max for PostgreSQL, taking 16 for the date _20151225_123456)
if (result.length() > MAX_SANITIZED_CASE_NAME_LEN) {
result = result.substring(0, MAX_SANITIZED_CASE_NAME_LEN);
}
if (result.isEmpty()) {
result = "case"; //NON-NLS
}
return result;
}
/**
* Creates a case directory and its subdirectories.
*
* @param caseDir Path to the case directory (typically base + case name).
* @param caseType The type of case, single-user or multi-user.
*
* @throws CaseActionException throw if could not create the case dir
*/
static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
File caseDirF = new File(caseDir);
if (caseDirF.exists()) {
if (caseDirF.isFile()) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
} else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
}
}
try {
boolean result = (caseDirF).mkdirs(); // create root case Directory
if (result == false) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
}
// create the folders inside the case directory
String hostClause = "";
if (caseType == CaseType.MULTI_USER_CASE) {
hostClause = File.separator + NetworkUtils.getLocalHostName();
}
result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
&& (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
&& (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
&& (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
if (result == false) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
}
final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
result = new File(modulesOutDir).mkdir();
if (result == false) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
modulesOutDir));
}
final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
result = new File(reportsOutDir).mkdir();
if (result == false) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
modulesOutDir));
}
} catch (Exception e) {
throw new CaseActionException(
NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
}
}
/**
* Opens an existing Autopsy case.
*
* @param caseMetadataFilePath The path of the case metadata (.aut) file.
*
* @throws CaseActionException if there is a problem opening the case. The
* exception will have a user-friendly message
* and may be a wrapper for a lower-level
* exception.
*/
public static void open(String caseMetadataFilePath) throws CaseActionException {
logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
/*
* Verify the extension of the case metadata file.
*/
if (!caseMetadataFilePath.endsWith(CaseMetadata.getFileExtension())) {
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CaseMetadata.getFileExtension()));
}
try {
/*
* Get the case metadata required to open the case database.
*/
CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
CaseType caseType = metadata.getCaseType();
/*
* Open the case database.
*/
SleuthkitCase db;
if (caseType == CaseType.SINGLE_USER_CASE) {
String dbPath = metadata.getCaseDatabasePath(); //NON-NLS
db = SleuthkitCase.openCase(dbPath);
} else {
if (!UserPreferences.getIsMultiUserModeEnabled()) {
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
}
try {
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
} catch (UserPreferencesException ex) {
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
}
}
/*
* Check for the presence of the UI and do things that can only be
* done with user interaction.
*/
if (RuntimeProperties.coreComponentsAreActive()) {
/*
* If the case database was upgraded for a new schema, notify
* the user.
*/
if (null != db.getBackupDatabasePath()) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(
WindowManager.getDefault().getMainWindow(),
NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", db.getBackupDatabasePath()),
NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
JOptionPane.INFORMATION_MESSAGE);
});
}
/*
* Look for the files for the data sources listed in the case
* database and give the user the opportunity to locate any that
* are missing.
*/
Map<Long, String> imgPaths = getImagePaths(db);
for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
long obj_id = entry.getKey();
String path = entry.getValue();
boolean fileExists = (new File(path).isFile() || driveExists(path));
if (!fileExists) {
int ret = JOptionPane.showConfirmDialog(
WindowManager.getDefault().getMainWindow(),
NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path),
NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
JOptionPane.YES_NO_OPTION);
if (ret == JOptionPane.YES_OPTION) {
MissingImageDialog.makeDialog(obj_id, db);
} else {
logger.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS
}
}
}
}
Case openedCase = new Case(metadata, db);
changeCurrentCase(openedCase);
} catch (CaseMetadataException ex) {
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS
} catch (TskCoreException ex) {
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
/*
* SleuthkitCase.openCase throws TskCoreExceptions with
* user-friendly messages, so propagate the exception message.
*/
throw new CaseActionException(ex.getMessage(), ex);
}
}
/**
* Gets the paths of data sources that are images.
*
* @param db A case database.
*
* @return A mapping of object ids to image paths.
*/
static Map<Long, String> getImagePaths(SleuthkitCase db) {
Map<Long, String> imgPaths = new HashMap<>();
try {
Map<Long, List<String>> imgPathsList = db.getImagePaths();
for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
if (entry.getValue().size() > 0) {
imgPaths.put(entry.getKey(), entry.getValue().get(0));
}
}
} catch (TskException ex) {
logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
}
return imgPaths;
}
/**
* Updates the current case to the given case, firing property change events
* and updating the UI.
*
* @param newCase The new current case or null if there is no new current
* case.
*/
private static void changeCurrentCase(Case newCase) {
// close the existing case
Case oldCase = Case.currentCase;
Case.currentCase = null;
if (oldCase != null) {
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
});
IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED);
completeCaseChange(null); //closes windows, etc
if (null != oldCase.tskErrorReporter) {
oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case
oldCase.tskErrorReporter = null;
}
eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), oldCase, null));
if (CaseType.MULTI_USER_CASE == oldCase.getCaseType()) {
if (null != oldCase.collaborationMonitor) {
oldCase.collaborationMonitor.shutdown();
}
eventPublisher.closeRemoteEventChannel();
}
}
if (newCase != null) {
currentCase = newCase;
Logger.setLogDirectory(currentCase.getLogDirectoryPath());
// sanity check
if (null != currentCase.tskErrorReporter) {
currentCase.tskErrorReporter.shutdown();
}
// start listening for TSK errors for the new case
currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECS_BETWEEN_TSK_ERROR_REPORTS,
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
completeCaseChange(currentCase);
SwingUtilities.invokeLater(() -> {
RecentCases.getInstance().addRecentCase(currentCase.getName(), currentCase.getCaseMetadata().getFilePath().toString()); // update the recent cases
});
if (CaseType.MULTI_USER_CASE == newCase.getCaseType()) {
try {
/**
* Use the text index name as the remote event channel name
* prefix since it is unique, the same as the case database
* name for a multiuser case, and is readily available
* through the Case.getTextIndexName() API.
*/
eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, newCase.getTextIndexName()));
currentCase.collaborationMonitor = new CollaborationMonitor();
} catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
logger.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS
MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"));
}
}
eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
} else {
Logger.setLogDirectory(PlatformUtil.getLogDirectory());
}
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
}
/**
* Updates the UI and does miscellaneous other things to complete changing
* the current case.
*
* @param newCase The new current case or null if there is no new current
* case.
*/
private static void completeCaseChange(Case newCase) {
logger.log(Level.INFO, "Changing Case to: {0}", newCase); //NON-NLS
if (newCase != null) { // new case is open
// clear the temp folder when the case is created / opened
Case.clearTempFolder();
if (RuntimeProperties.coreComponentsAreActive()) {
// enable these menus
SwingUtilities.invokeLater(() -> {
CallableSystemAction.get(AddImageAction.class).setEnabled(true);
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
if (newCase.hasData()) {
// open all top components
CoreComponentControl.openCoreWindows();
} else {
// close all top components
CoreComponentControl.closeCoreWindows();
}
addCaseNameToMainWindowTitle(currentCase.getName());
});
} else {
SwingUtilities.invokeLater(() -> {
Frame f = WindowManager.getDefault().getMainWindow();
f.setTitle(getAppName()); // set the window name to just application name
});
}
} else { // case is closed
SwingUtilities.invokeLater(() -> {
if (RuntimeProperties.coreComponentsAreActive()) {
// close all top components first
CoreComponentControl.closeCoreWindows();
// disable these menus
CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
}
//clear pending notifications
MessageNotifyUtil.Notify.clear();
Frame f = WindowManager.getDefault().getMainWindow();
f.setTitle(getAppName()); // set the window name to just application name
});
//try to force gc to happen
System.gc();
System.gc();
}
//log memory usage after case changed
logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
}
/**
* Empties the temp subdirectory for the current case.
*/
private static void clearTempFolder() {
File tempFolder = new File(currentCase.getTempDirectory());
if (tempFolder.isDirectory()) {
File[] files = tempFolder.listFiles();
if (files.length > 0) {
for (File file : files) {
if (file.isDirectory()) {
deleteCaseDirectory(file);
} else {
file.delete();
}
}
}
}
}
/**
* Changes the title of the main window to include the case name.
*
* @param newCaseName The name of the case.
*/
private static void addCaseNameToMainWindowTitle(String newCaseName) {
if (!newCaseName.equals("")) {
Frame f = WindowManager.getDefault().getMainWindow();
f.setTitle(newCaseName + " - " + getAppName());
}
}
/**
* Deletes a case directory.
*
* @param casePath A case directory path.
*
* @return True if the deleteion succeeded, false otherwise.
*/
static boolean deleteCaseDirectory(File casePath) {
logger.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS
return FileUtil.deleteDir(casePath);
}
/**
* Gets the time zone(s) of the image data source(s) in this case.
*
* @return The set of time zones in use.
*
* @deprecated Use getTimeZones instead.
*/
@Deprecated
public Set<TimeZone> getTimeZone() {
return getTimeZones();
}
/**
* Determines whether or not a given path is for a physical drive.
*
* @param path The path to test.
*
* @return True or false.
*
* @deprecated Use
* org.sleuthkit.autopsy.coreutils.DriveUtils.isPhysicalDrive instead.
*/
@Deprecated
static boolean isPhysicalDrive(String path) {
return DriveUtils.isPhysicalDrive(path);
}
/**
* Determines whether or not a given path is for a local drive or partition.
*
* @param path The path to test.
*
* @deprecated Use org.sleuthkit.autopsy.coreutils.DriveUtils.isPartition
* instead.
*/
@Deprecated
static boolean isPartition(String path) {
return DriveUtils.isPartition(path);
}
/**
* Determines whether or not a drive exists by eading the first byte and
* checking if it is a -1.
*
* @param path The path to test.
*
* @return True or false.
*
* @deprecated Use org.sleuthkit.autopsy.coreutils.DriveUtils.driveExists
* instead.
*/
@Deprecated
static boolean driveExists(String path) {
return DriveUtils.driveExists(path);
}
/**
* Invokes the startup dialog window.
*
* @deprecated Use StartupWindowProvider.getInstance().open() instead.
*/
@Deprecated
public static void invokeStartupDialog() {
StartupWindowProvider.getInstance().open();
}
/**
* Converts a Java timezone id to a coded string with only alphanumeric
* characters. Example: "America/New_York" is converted to "EST5EDT" by this
* method.
*
* @param timeZoneId The time zone id.
*
* @return The converted time zone string.
*
* @deprecated Use
* org.sleuthkit.autopsy.coreutils.TimeZoneUtils.convertToAlphaNumericFormat
* instead.
*/
@Deprecated
public static String convertTimeZone(String timeZoneId) {
return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
}
/**
* Check if file exists and is a normal file.
*
* @param filePath The file path.
*
* @return True or false.
*
* @deprecated Use java.io.File.exists or java.io.File.isFile instead
*/
@Deprecated
public static boolean pathExists(String filePath) {
return new File(filePath).isFile();
}
/**
* Gets the Autopsy version.
*
* @return The Autopsy version.
*
* @deprecated Use org.sleuthkit.autopsy.coreutils.Version.getVersion
* instead
*/
@Deprecated
public static String getAutopsyVersion() {
return Version.getVersion();
}
/**
* Creates an Autopsy case directory.
*
* @param caseDir Path to the case directory (typically base + case name)
* @param caseName the case name (used only for error messages)
*
* @throws CaseActionException
* @Deprecated Use createCaseDirectory(String caseDir, CaseType caseType)
* instead
*/
@Deprecated
static void createCaseDirectory(String caseDir, String caseName) throws CaseActionException {
createCaseDirectory(caseDir, CaseType.SINGLE_USER_CASE);
}
/**
* Check if case is currently open.
*
* @return True if a case is open.
*
* @deprecated Use isCaseOpen instead.
*/
@Deprecated
public static boolean existsCurrentCase() {
return currentCase != null;
}
/**
* Get module output directory path where modules should save their
* permanent data.
*
* @return absolute path to the module output directory
*
* @deprecated Use getModuleDirectory() instead.
*/
@Deprecated
public String getModulesOutputDirAbsPath() {
return getModuleDirectory();
}
/**
* Get relative (with respect to case dir) module output directory path
* where modules should save their permanent data. The directory is a
* subdirectory of this case dir.
*
* @return relative path to the module output dir
*
* @deprecated Use getModuleOutputDirectoryRelativePath() instead
*/
@Deprecated
public static String getModulesOutputDirRelPath() {
return "ModuleOutput"; //NON-NLS
}
/**
* Gets a PropertyChangeSupport object. The PropertyChangeSupport object
* returned is not used by instances of this class and does not have any
* PropertyChangeListeners.
*
* @return A new PropertyChangeSupport object.
*
* @deprecated Do not use.
*/
@Deprecated
public static PropertyChangeSupport getPropertyChangeSupport() {
return new PropertyChangeSupport(Case.class);
}
/**
* Gets the full path to the case metadata file for this case.
*
* @return configFilePath The case metadata file path.
*
* @deprecated Use getCaseMetadata and CaseMetadata.getFilePath instead.
*/
@Deprecated
String getConfigFilePath() {
return getCaseMetadata().getFilePath().toString();
}
/**
* Adds an image to the current case after it has been added to the DB.
* Sends out event and reopens windows if needed.
*
* @param imgPath The path of the image file.
* @param imgId The ID of the image.
* @param timeZone The time zone of the image.
*
* @deprecated As of release 4.0
*/
@Deprecated
public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
try {
Image newDataSource = db.getImageById(imgId);
notifyDataSourceAdded(newDataSource, UUID.randomUUID());
return newDataSource;
} catch (Exception ex) {
throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
}
}
/**
* Finishes adding new local data source to the case. Sends out event and
* reopens windows if needed.
*
* @param newDataSource new data source added
*
* @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
* {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
*/
@Deprecated
void addLocalDataSource(Content newDataSource) {
notifyDataSourceAdded(newDataSource, UUID.randomUUID());
}
/**
* Deletes reports from the case.
*
* @param reports Collection of Report to be deleted from the case.
* @param deleteFromDisk No longer supported - ignored.
*
* @throws TskCoreException
* @deprecated Use deleteReports(Collection<? extends Report> reports)
* instead.
*/
@Deprecated
public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
deleteReports(reports);
}
@Deprecated
public static final String propStartup = "LBL_StartupDialog"; //NON-NLS
}