Autopsy  4.21.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Case.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-2021 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.casemodule;
20 
23 import com.google.common.annotations.Beta;
24 import com.google.common.eventbus.Subscribe;
25 import com.google.common.util.concurrent.ThreadFactoryBuilder;
26 import java.awt.Cursor;
28 import java.awt.Frame;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.beans.PropertyChangeListener;
32 import java.beans.PropertyChangeSupport;
33 import java.io.File;
34 import java.lang.reflect.InvocationTargetException;
35 import java.nio.file.InvalidPathException;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.sql.Connection;
39 import java.sql.DriverManager;
40 import java.sql.ResultSet;
41 import java.sql.SQLException;
42 import java.sql.Statement;
43 import java.text.SimpleDateFormat;
44 import java.util.Collection;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.TimeZone;
52 import java.util.UUID;
53 import java.util.concurrent.CancellationException;
54 import java.util.concurrent.ExecutionException;
55 import java.util.concurrent.ExecutorService;
56 import java.util.concurrent.Executors;
57 import java.util.concurrent.Future;
58 import java.util.concurrent.ThreadFactory;
59 import java.util.concurrent.TimeUnit;
60 import java.util.logging.Level;
61 import java.util.stream.Collectors;
62 import java.util.stream.Stream;
63 import javax.annotation.concurrent.GuardedBy;
64 import javax.annotation.concurrent.ThreadSafe;
65 import javax.swing.JOptionPane;
66 import javax.swing.SwingUtilities;
67 import org.apache.commons.lang3.StringUtils;
68 import org.openide.util.Lookup;
69 import org.openide.util.NbBundle;
70 import org.openide.util.NbBundle.Messages;
71 import org.openide.util.actions.CallableSystemAction;
72 import org.openide.windows.WindowManager;
144 import org.sleuthkit.datamodel.BlackboardArtifactTag;
145 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
146 import org.sleuthkit.datamodel.Content;
147 import org.sleuthkit.datamodel.ContentStreamProvider;
148 import org.sleuthkit.datamodel.ContentTag;
149 import org.sleuthkit.datamodel.DataSource;
150 import org.sleuthkit.datamodel.FileSystem;
151 import org.sleuthkit.datamodel.Image;
152 import org.sleuthkit.datamodel.Report;
153 import org.sleuthkit.datamodel.SleuthkitCase;
154 import org.sleuthkit.datamodel.TimelineManager;
155 import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
156 import org.sleuthkit.datamodel.TskCoreException;
157 import org.sleuthkit.datamodel.TskDataException;
158 import org.sleuthkit.datamodel.TskEvent;
159 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
160 
164 public class Case {
165 
166  private static final int CASE_LOCK_TIMEOUT_MINS = 1;
167  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
168  private static final String APP_NAME = UserPreferences.getAppName();
169  private static final String TEMP_FOLDER = "Temp";
170  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
171  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
172  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
173  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
174  private static final String LOG_FOLDER = "Log"; //NON-NLS
175  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
176  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
177  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
178  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
179  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
180  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
181  private static final String CT_PROVIDER_PREFIX = "CTStandardContentProvider_";
182  private static final Logger logger = Logger.getLogger(Case.class.getName());
184  private static final Object caseActionSerializationLock = new Object();
185  private static Future<?> backgroundOpenFileSystemsFuture = null;
186  private static final ExecutorService openFileSystemsExecutor
187  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
188  private static volatile Frame mainFrame;
189  private static volatile Case currentCase;
190  private final CaseMetadata metadata;
191  private volatile ExecutorService caseActionExecutor;
193  private SleuthkitCase caseDb;
195  private CollaborationMonitor collaborationMonitor;
197  // matches something like '\\.\PHYSICALDRIVE0'
198  private static final String PLACEHOLDER_DS_PATH_REGEX = "^\\s*\\\\\\\\\\.\\\\PHYSICALDRIVE\\d*\\s*$";
199 
200  private volatile boolean hasDataSource = false;
201  private volatile boolean hasData = false;
202 
203  /*
204  * Get a reference to the main window of the desktop application to use to
205  * parent pop up dialogs and initialize the application name for use in
206  * changing the main window title.
207  */
208  static {
209  WindowManager.getDefault().invokeWhenUIReady(() -> {
210  mainFrame = WindowManager.getDefault().getMainWindow();
211  });
212  }
213 
217  public enum CaseType {
218 
219  SINGLE_USER_CASE("Single-user case"), //NON-NLS
220  MULTI_USER_CASE("Multi-user case"); //NON-NLS
221 
222  private final String typeName;
223 
231  public static CaseType fromString(String typeName) {
232  if (typeName != null) {
233  for (CaseType c : CaseType.values()) {
234  if (typeName.equalsIgnoreCase(c.toString())) {
235  return c;
236  }
237  }
238  }
239  return null;
240  }
241 
247  @Override
248  public String toString() {
249  return typeName;
250  }
251 
257  @Messages({
258  "Case_caseType_singleUser=Single-user case",
259  "Case_caseType_multiUser=Multi-user case"
260  })
262  if (fromString(typeName) == SINGLE_USER_CASE) {
263  return Bundle.Case_caseType_singleUser();
264  } else {
265  return Bundle.Case_caseType_multiUser();
266  }
267  }
268 
274  private CaseType(String typeName) {
275  this.typeName = typeName;
276  }
277 
288  @Deprecated
289  public boolean equalsName(String otherTypeName) {
290  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
291  }
292 
293  };
294 
299  public enum Events {
300 
308  @Deprecated
317  @Deprecated
326  @Deprecated
437  /*
438  * An item in the central repository has had its comment modified. The
439  * old value is null, the new value is string for current comment.
440  */
490 
495 
500 
505 
510 
515 
516  };
517 
523  private final class SleuthkitEventListener {
524 
525  @Subscribe
526  public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
527  eventPublisher.publish(new TimelineEventAddedEvent(event));
528  }
529 
530  @Subscribe
531  public void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event) {
532  hasData = true;
533  eventPublisher.publish(new OsAccountsAddedEvent(event.getOsAcounts()));
534  }
535 
536  @Subscribe
537  public void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event) {
538  eventPublisher.publish(new OsAccountsUpdatedEvent(event.getOsAcounts()));
539  }
540 
541  @Subscribe
542  public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) {
543  try {
544  hasData = dbHasData();
545  } catch (TskCoreException ex) {
546  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
547  }
548  eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds()));
549  }
550 
551  @Subscribe
552  public void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event) {
553  eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances()));
554  }
555 
562  @Subscribe
563  public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) {
564  hasData = true;
565  eventPublisher.publish(new HostsAddedEvent(event.getHosts()));
566  }
567 
574  @Subscribe
575  public void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event) {
576  eventPublisher.publish(new HostsUpdatedEvent(event.getHosts()));
577  }
578 
585  @Subscribe
586  public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) {
587  try {
588  hasData = dbHasData();
589  } catch (TskCoreException ex) {
590  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
591  }
592 
593  eventPublisher.publish(new HostsDeletedEvent(event.getHostIds()));
594  }
595 
602  @Subscribe
603  public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) {
604  eventPublisher.publish(new PersonsAddedEvent(event.getPersons()));
605  }
606 
613  @Subscribe
614  public void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event) {
615  eventPublisher.publish(new PersonsUpdatedEvent(event.getPersons()));
616  }
617 
624  @Subscribe
625  public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) {
626  eventPublisher.publish(new PersonsDeletedEvent(event.getPersonIds()));
627  }
628 
629  @Subscribe
630  public void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event) {
631  eventPublisher.publish(new HostsAddedToPersonEvent(event.getPerson(), event.getHosts()));
632  }
633 
634  @Subscribe
635  public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) {
636  eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds()));
637  }
638 
639  @Subscribe
640  public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) {
641  eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames()));
642  }
643 
644  @Subscribe
645  public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) {
646  eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames()));
647  }
648 
649  @Subscribe
650  public void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event) {
651  eventPublisher.publish(new TagNamesDeletedEvent(event.getTagNameIds()));
652  }
653 
654  @Subscribe
655  public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) {
656  eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets()));
657  }
658 
659  @Subscribe
660  public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) {
661  eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds()));
662  }
663  }
664 
671  public static void addPropertyChangeListener(PropertyChangeListener listener) {
672  addEventSubscriber(Stream.of(Events.values())
673  .map(Events::toString)
674  .collect(Collectors.toSet()), listener);
675  }
676 
683  public static void removePropertyChangeListener(PropertyChangeListener listener) {
684  removeEventSubscriber(Stream.of(Events.values())
685  .map(Events::toString)
686  .collect(Collectors.toSet()), listener);
687  }
688 
697  @Deprecated
698  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
699  eventPublisher.addSubscriber(eventNames, subscriber);
700  }
701 
708  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
709  eventTypes.forEach((Events event) -> {
710  eventPublisher.addSubscriber(event.toString(), subscriber);
711  });
712  }
713 
722  @Deprecated
723  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
724  eventPublisher.addSubscriber(eventName, subscriber);
725  }
726 
733  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
734  eventPublisher.removeSubscriber(eventName, subscriber);
735  }
736 
743  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
744  eventPublisher.removeSubscriber(eventNames, subscriber);
745  }
746 
753  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
754  eventTypes.forEach((Events event) -> {
755  eventPublisher.removeSubscriber(event.toString(), subscriber);
756  });
757  }
758 
767  public static boolean isValidName(String caseName) {
768  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
769  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
770  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
771  }
772 
797  @Deprecated
798  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
799  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
800  }
801 
821  @Messages({
822  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
823  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
824  })
825  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
826  if (caseDetails.getCaseDisplayName().isEmpty()) {
827  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
828  }
829  if (caseDir.isEmpty()) {
830  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
831  }
832  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
833  }
834 
848  @Messages({
849  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
850  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
851  })
852  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
854  try {
855  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
856  } catch (CaseMetadataException ex) {
857  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
858  }
860  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
861  }
862  openAsCurrentCase(new Case(metadata), false);
863  }
864 
870  public static boolean isCaseOpen() {
871  return currentCase != null;
872  }
873 
881  public static Case getCurrentCase() {
882  try {
883  return getCurrentCaseThrows();
884  } catch (NoCurrentCaseException ex) {
885  /*
886  * Throw a runtime exception, since this is a programming error.
887  */
888  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
889  }
890  }
891 
907  /*
908  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
909  * case method.
910  */
911  Case openCase = currentCase;
912  if (openCase == null) {
913  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
914  } else {
915  return openCase;
916  }
917  }
918 
927  @Messages({
928  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
929  "Case.progressIndicatorTitle.closingCase=Closing Case"
930  })
931  public static void closeCurrentCase() throws CaseActionException {
932  synchronized (caseActionSerializationLock) {
933  if (null == currentCase) {
934  return;
935  }
936  Case closedCase = currentCase;
937  try {
938  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
939  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
940  closedCase.doCloseCaseAction();
941  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
942  } catch (CaseActionException ex) {
943  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
944  throw ex;
945  } finally {
946  currentCase = null;
949  }
950  }
951  }
952  }
953 
962  public static void deleteCurrentCase() throws CaseActionException {
963  synchronized (caseActionSerializationLock) {
964  if (null == currentCase) {
965  return;
966  }
967  CaseMetadata metadata = currentCase.getMetadata();
969  deleteCase(metadata);
970  }
971  }
972 
983  @Messages({
984  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
985  })
986  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
987  synchronized (caseActionSerializationLock) {
988  if (null == currentCase) {
989  return;
990  }
991 
992  /*
993  * Close the current case to release the shared case lock.
994  */
995  CaseMetadata caseMetadata = currentCase.getMetadata();
997 
998  /*
999  * Re-open the case with an exclusive case lock, delete the data
1000  * source, and close the case again, releasing the exclusive case
1001  * lock.
1002  */
1003  Case theCase = new Case(caseMetadata);
1004  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
1005 
1006  /*
1007  * Re-open the case with a shared case lock.
1008  */
1009  openAsCurrentCase(new Case(caseMetadata), false);
1010  }
1011  }
1012 
1024  @Messages({
1025  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
1026  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
1027  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
1028  })
1029  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
1030  synchronized (caseActionSerializationLock) {
1031  if (null != currentCase) {
1032  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
1033  }
1034  }
1035 
1036  ProgressIndicator progressIndicator;
1038  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
1039  } else {
1040  progressIndicator = new LoggingProgressIndicator();
1041  }
1042  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1043  try {
1044  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1045  deleteSingleUserCase(metadata, progressIndicator);
1046  } else {
1047  try {
1048  deleteMultiUserCase(metadata, progressIndicator);
1049  } catch (InterruptedException ex) {
1050  /*
1051  * Note that task cancellation is not currently supported
1052  * for this code path, so this catch block is not expected
1053  * to be executed.
1054  */
1055  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
1056  }
1057  }
1058  } finally {
1059  progressIndicator.finish();
1060  }
1061  }
1062 
1073  @Messages({
1074  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1075  "Case.progressIndicatorTitle.openingCase=Opening Case",
1076  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
1077  })
1078  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
1079  synchronized (caseActionSerializationLock) {
1080  if (null != currentCase) {
1081  try {
1082  closeCurrentCase();
1083  } catch (CaseActionException ex) {
1084  /*
1085  * Notify the user and continue (the error has already been
1086  * logged in closeCurrentCase.
1087  */
1088  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
1089  }
1090  }
1091  try {
1092  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1093  String progressIndicatorTitle;
1095  if (isNewCase) {
1096  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
1097  openCaseAction = newCurrentCase::create;
1098  } else {
1099  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
1100  openCaseAction = newCurrentCase::open;
1101  }
1102  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
1103  currentCase = newCurrentCase;
1104  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1106  updateGUIForCaseOpened(newCurrentCase);
1107  }
1108  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
1109  } catch (CaseActionCancelledException ex) {
1110  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
1111  throw ex;
1112  } catch (CaseActionException ex) {
1113  logger.log(Level.SEVERE, String.format("Error opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()), ex); //NON-NLS
1114  throw ex;
1115  }
1116  }
1117  }
1118 
1127  private static String displayNameToUniqueName(String caseDisplayName) {
1128  /*
1129  * Replace all non-ASCII characters.
1130  */
1131  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
1132 
1133  /*
1134  * Replace all control characters.
1135  */
1136  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
1137 
1138  /*
1139  * Replace /, \, :, ?, space, ' ".
1140  */
1141  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
1142 
1143  /*
1144  * Make it all lowercase.
1145  */
1146  uniqueCaseName = uniqueCaseName.toLowerCase();
1147 
1148  /*
1149  * Add a time stamp for uniqueness.
1150  */
1151  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
1152  Date date = new Date();
1153  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
1154 
1155  return uniqueCaseName;
1156  }
1157 
1167  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
1168  /*
1169  * Check the case directory path and permissions. The case directory may
1170  * already exist.
1171  */
1172  File caseDir = new File(caseDirPath);
1173  if (caseDir.exists()) {
1174  if (caseDir.isFile()) {
1175  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
1176  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
1177  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
1178  }
1179  }
1180 
1181  /*
1182  * Create the case directory, if it does not already exist.
1183  */
1184  if (!caseDir.mkdirs()) {
1185  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
1186  }
1187 
1188  /*
1189  * Create the subdirectories of the case directory, if they do not
1190  * already exist. Note that multi-user cases get an extra layer of
1191  * subdirectories, one subdirectory per application host machine.
1192  */
1193  String hostPathComponent = "";
1194  if (caseType == CaseType.MULTI_USER_CASE) {
1195  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
1196  }
1197 
1198  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
1199  if (!exportDir.toFile().mkdirs()) {
1200  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
1201  }
1202 
1203  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
1204  if (!logsDir.toFile().mkdirs()) {
1205  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
1206  }
1207 
1208  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
1209  if (!cacheDir.toFile().mkdirs()) {
1210  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
1211  }
1212 
1213  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
1214  if (!moduleOutputDir.toFile().mkdirs()) {
1215  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
1216  }
1217 
1218  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1219  if (!reportsDir.toFile().mkdirs()) {
1220  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1221  }
1222  }
1223 
1231  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1232  Map<Long, String> imgPaths = new HashMap<>();
1233  try {
1234  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1235  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1236  if (entry.getValue().size() > 0) {
1237  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1238  }
1239  }
1240  } catch (TskCoreException ex) {
1241  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1242  }
1243  return imgPaths;
1244  }
1245 
1256  @Messages({
1257  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1258  })
1259  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1260  try {
1261  Path caseDirPath = Paths.get(caseDir);
1262  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1263  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1264  return lock;
1265  } catch (InterruptedException ex) {
1266  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1267  } catch (CoordinationServiceException ex) {
1268  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1269  }
1270  }
1271 
1272  private static String getNameForTitle() {
1273  //Method should become unnecessary once technical debt story 3334 is done.
1274  if (UserPreferences.getAppName().equals(Version.getName())) {
1275  //Available version number is version number for this application
1276  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1277  } else {
1278  return UserPreferences.getAppName();
1279  }
1280  }
1281 
1285  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1286  /*
1287  * If the case database was upgraded for a new schema and a backup
1288  * database was created, notify the user.
1289  */
1290  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1291  String backupDbPath = caseDb.getBackupDatabasePath();
1292  if (null != backupDbPath) {
1293  JOptionPane.showMessageDialog(
1294  mainFrame,
1295  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1296  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1297  JOptionPane.INFORMATION_MESSAGE);
1298  }
1299 
1300  /*
1301  * Look for the files for the data sources listed in the case database
1302  * and give the user the opportunity to locate any that are missing.
1303  */
1304  Map<Long, String> imgPaths = getImagePaths(caseDb);
1305  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1306  long obj_id = entry.getKey();
1307  String path = entry.getValue();
1308  boolean fileExists = (new File(path).exists()|| DriveUtils.driveExists(path));
1309  if (!fileExists) {
1310  // CT-7336: ignore relocating datasources if file provider is present and placeholder path is used.
1311  if (newCurrentCase.getMetadata() != null
1312  && !StringUtils.isBlank(newCurrentCase.getMetadata().getContentProviderName())
1313  && (path == null || path.matches(PLACEHOLDER_DS_PATH_REGEX))) {
1314  continue;
1315  }
1316 
1317  try {
1318  DataSource ds = newCurrentCase.getSleuthkitCase().getDataSource(obj_id);
1319  String hostName = StringUtils.defaultString(ds.getHost() == null ? "" : ds.getHost().getName());
1320  // Using invokeAndWait means that the dialog will
1321  // open on the EDT but this thread will wait for an
1322  // answer. Using invokeLater would cause this loop to
1323  // end before all of the dialogs appeared.
1324  SwingUtilities.invokeAndWait(new Runnable() {
1325  @Override
1326  public void run() {
1327  int response = JOptionPane.showConfirmDialog(
1328  mainFrame,
1329  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", hostName, path),
1330  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1331  JOptionPane.YES_NO_OPTION);
1332  if (response == JOptionPane.YES_OPTION) {
1333  MissingImageDialog.makeDialog(obj_id, caseDb);
1334  } else {
1335  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1336 
1337  }
1338  }
1339 
1340  });
1341  } catch (InterruptedException | InvocationTargetException | TskCoreException | TskDataException ex) {
1342  logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
1343  }
1344  }
1345  }
1346 
1347  /*
1348  * Enable the case-specific actions.
1349  */
1350  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1351  CallableSystemAction.get(OpenHostsAction.class).setEnabled(true);
1352  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1353  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1354  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1355  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1356  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1357  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1358  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1359  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1360  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1361 
1362  /*
1363  * Add the case to the recent cases tracker that supplies a list of
1364  * recent cases to the recent cases menu item and the open/create case
1365  * dialog.
1366  */
1367  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1368  final boolean hasData = newCurrentCase.hasData();
1369 
1370  SwingUtilities.invokeLater(() -> {
1371  /*
1372  * Open the top components (windows within the main application
1373  * window).
1374  *
1375  * Note: If the core windows are not opened here, they will be
1376  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1377  * method on a DATA_SOURCE_ADDED event.
1378  */
1379  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1380  if (hasData) {
1382  } else {
1383  //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible.
1385  }
1386  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
1387 
1388  /*
1389  * Reset the main window title to:
1390  *
1391  * [curent case display name] - [application name].
1392  */
1393  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1394  });
1395  }
1396 
1397  /*
1398  * Update the GUI to to reflect the lack of a current case.
1399  */
1400  private static void updateGUIForCaseClosed() {
1402  SwingUtilities.invokeLater(() -> {
1403  /*
1404  * Close the top components (windows within the main application
1405  * window).
1406  */
1408 
1409  /*
1410  * Disable the case-specific menu items.
1411  */
1412  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1413  CallableSystemAction.get(OpenHostsAction.class).setEnabled(false);
1414  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1415  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1416  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1417  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1418  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1419  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1420  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1421  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1422  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1423 
1424  /*
1425  * Clear the notifications in the notfier component in the lower
1426  * right hand corner of the main application window.
1427  */
1429 
1430  /*
1431  * Reset the main window title to be just the application name,
1432  * instead of [curent case display name] - [application name].
1433  */
1434  mainFrame.setTitle(getNameForTitle());
1435  });
1436  }
1437  }
1438 
1444  public SleuthkitCase getSleuthkitCase() {
1445  return this.caseDb;
1446  }
1447 
1454  return caseServices;
1455  }
1456 
1463  return metadata.getCaseType();
1464  }
1465 
1471  public String getCreatedDate() {
1472  return metadata.getCreatedDate();
1473  }
1474 
1480  public String getName() {
1481  return metadata.getCaseName();
1482  }
1483 
1489  public String getDisplayName() {
1490  return metadata.getCaseDisplayName();
1491  }
1492 
1498  public String getNumber() {
1499  return metadata.getCaseNumber();
1500  }
1501 
1507  public String getExaminer() {
1508  return metadata.getExaminer();
1509  }
1510 
1516  public String getExaminerPhone() {
1517  return metadata.getExaminerPhone();
1518  }
1519 
1525  public String getExaminerEmail() {
1526  return metadata.getExaminerEmail();
1527  }
1528 
1534  public String getCaseNotes() {
1535  return metadata.getCaseNotes();
1536  }
1537 
1543  public String getCaseDirectory() {
1544  return metadata.getCaseDirectory();
1545  }
1546 
1555  public String getOutputDirectory() {
1556  String caseDirectory = getCaseDirectory();
1557  Path hostPath;
1558  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1559  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1560  } else {
1561  hostPath = Paths.get(caseDirectory);
1562  }
1563  if (!hostPath.toFile().exists()) {
1564  hostPath.toFile().mkdirs();
1565  }
1566  return hostPath.toString();
1567  }
1568 
1572  private Path getBaseSystemTempPath() {
1573  return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName());
1574  }
1575 
1582  public String getTempDirectory() {
1583  // NOTE: UserPreferences may also be affected by changes in this method.
1584  // See JIRA-7505 for more information.
1585  Path basePath = null;
1586  // get base temp path for the case based on user preference
1588  case CUSTOM:
1589  String customDirectory = UserMachinePreferences.getCustomTempDirectory();
1590  basePath = (StringUtils.isBlank(customDirectory))
1591  ? null
1592  : Paths.get(customDirectory, APP_NAME, getName());
1593  break;
1594  case CASE:
1595  basePath = Paths.get(getCaseDirectory());
1596  break;
1597  case SYSTEM:
1598  default:
1599  // at this level, if the case directory is specified for a temp
1600  // directory, return the system temp directory instead.
1601  basePath = getBaseSystemTempPath();
1602  break;
1603  }
1604 
1605  basePath = basePath == null ? getBaseSystemTempPath() : basePath;
1606 
1607  // get sub directories based on multi user vs. single user
1608  Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType()))
1609  ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER)
1610  : Paths.get(TEMP_FOLDER);
1611 
1612  File caseTempDir = basePath
1613  .resolve(caseRelPath)
1614  .toFile();
1615 
1616  // ensure directory exists
1617  if (!caseTempDir.exists()) {
1618  caseTempDir.mkdirs();
1619  }
1620 
1621  return caseTempDir.getAbsolutePath();
1622  }
1623 
1630  public String getCacheDirectory() {
1631  return getOrCreateSubdirectory(CACHE_FOLDER);
1632  }
1633 
1640  public String getExportDirectory() {
1641  return getOrCreateSubdirectory(EXPORT_FOLDER);
1642  }
1643 
1650  public String getLogDirectoryPath() {
1651  return getOrCreateSubdirectory(LOG_FOLDER);
1652  }
1653 
1660  public String getReportDirectory() {
1661  return getOrCreateSubdirectory(REPORTS_FOLDER);
1662  }
1663 
1670  public String getConfigDirectory() {
1671  return getOrCreateSubdirectory(CONFIG_FOLDER);
1672  }
1673 
1680  public String getModuleDirectory() {
1681  return getOrCreateSubdirectory(MODULE_FOLDER);
1682  }
1683 
1692  Path path = Paths.get(getModuleDirectory());
1694  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1695  } else {
1696  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1697  }
1698  }
1699 
1709  public List<Content> getDataSources() throws TskCoreException {
1710  return caseDb.getRootObjects();
1711  }
1712 
1718  public Set<TimeZone> getTimeZones() {
1719  Set<TimeZone> timezones = new HashSet<>();
1720  String query = "SELECT time_zone FROM data_source_info";
1721  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1722  ResultSet timeZoneSet = dbQuery.getResultSet();
1723  while (timeZoneSet.next()) {
1724  String timeZone = timeZoneSet.getString("time_zone");
1725  if (timeZone != null && !timeZone.isEmpty()) {
1726  timezones.add(TimeZone.getTimeZone(timeZone));
1727  }
1728  }
1729  } catch (TskCoreException | SQLException ex) {
1730  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1731  }
1732  return timezones;
1733  }
1734 
1741  public String getTextIndexName() {
1742  return getMetadata().getTextIndexName();
1743  }
1744 
1750  public boolean hasData() {
1751  return hasData;
1752  }
1753 
1759  public boolean hasDataSource() {
1760  return hasDataSource;
1761  }
1762 
1773  public void notifyAddingDataSource(UUID eventId) {
1774  hasDataSource = true;
1775  hasData = true;
1776  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1777  }
1778 
1789  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1790  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1791  }
1792 
1804  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1805  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1806  }
1807 
1817  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1818  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1819  }
1820 
1828  public void notifyContentTagAdded(ContentTag newTag) {
1829  notifyContentTagAdded(newTag, null);
1830  }
1831 
1841  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1842  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1843  }
1844 
1852  public void notifyContentTagDeleted(ContentTag deletedTag) {
1853  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1854  }
1855 
1863  public void notifyTagDefinitionChanged(String changedTagName) {
1864  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1865  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1866  }
1867 
1878  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1879  try {
1880  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1881  } catch (NoCurrentCaseException ex) {
1882  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1883  }
1884  }
1885 
1893  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1894  notifyBlackBoardArtifactTagAdded(newTag, null);
1895  }
1896 
1906  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1907  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1908  }
1909 
1917  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1918  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1919  }
1920 
1932  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1933  addReport(localPath, srcModuleName, reportName, null);
1934  }
1935 
1950  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1951  String normalizedLocalPath;
1952  try {
1953  if (localPath.toLowerCase().contains("http:")) {
1954  normalizedLocalPath = localPath;
1955  } else {
1956  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1957  }
1958  } catch (InvalidPathException ex) {
1959  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1960  throw new TskCoreException(errorMsg, ex);
1961  }
1962  hasData = true;
1963 
1964  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1965  eventPublisher.publish(new ReportAddedEvent(report));
1966  return report;
1967  }
1968 
1977  public List<Report> getAllReports() throws TskCoreException {
1978  return this.caseDb.getAllReports();
1979  }
1980 
1989  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1990  for (Report report : reports) {
1991  this.caseDb.deleteReport(report);
1992  }
1993 
1994  try {
1995  hasData = dbHasData();
1996  } catch (TskCoreException ex) {
1997  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
1998  }
1999 
2000  for (Report report : reports) {
2001  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
2002  }
2003  }
2004 
2011  return metadata;
2012  }
2013 
2021  @Messages({
2022  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
2023  })
2024  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
2025  CaseDetails oldCaseDetails = metadata.getCaseDetails();
2026  try {
2027  metadata.setCaseDetails(caseDetails);
2028  } catch (CaseMetadataException ex) {
2029  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
2030  }
2031  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2032  try {
2033  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2034  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
2035  CaseNodeData.writeCaseNodeData(nodeData);
2036  } catch (CaseNodeDataException | InterruptedException ex) {
2037  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2038  }
2039  }
2040  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
2041  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
2042  }
2043  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
2044  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
2045  }
2046  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2047  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
2048  }
2049  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
2050  if (RuntimeProperties.runningWithGUI()) {
2051  SwingUtilities.invokeLater(() -> {
2052  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
2053  try {
2054  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
2055  } catch (Exception ex) {
2056  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
2057  }
2058  });
2059  }
2060  }
2061 
2074  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
2075  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
2076  }
2077 
2083  private Case(CaseMetadata caseMetaData) {
2084  metadata = caseMetaData;
2085  sleuthkitEventListener = new SleuthkitEventListener();
2086  }
2087 
2117  @Messages({
2118  "Case.progressIndicatorCancelButton.label=Cancel",
2119  "Case.progressMessage.preparing=Preparing...",
2120  "Case.progressMessage.cancelling=Cancelling...",
2121  "Case.exceptionMessage.cancelled=Cancelled.",
2122  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
2123  })
2124  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
2125  /*
2126  * Create and start either a GUI progress indicator (with or without a
2127  * cancel button) or a logging progress indicator.
2128  */
2129  CancelButtonListener cancelButtonListener = null;
2130  ProgressIndicator progressIndicator;
2132  if (allowCancellation) {
2133  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
2134  progressIndicator = new ModalDialogProgressIndicator(
2135  mainFrame,
2136  progressIndicatorTitle,
2137  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2138  Bundle.Case_progressIndicatorCancelButton_label(),
2139  cancelButtonListener);
2140  } else {
2141  progressIndicator = new ModalDialogProgressIndicator(
2142  mainFrame,
2143  progressIndicatorTitle);
2144  }
2145  } else {
2146  progressIndicator = new LoggingProgressIndicator();
2147  }
2148  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2149 
2150  /*
2151  * Do the case action in the single thread in the case action executor.
2152  * If the case is a multi-user case, a case lock is acquired and held
2153  * until explictly released and an exclusive case resources lock is
2154  * aquired and held for the duration of the action.
2155  */
2156  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
2157  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
2158  Future<Void> future = caseActionExecutor.submit(() -> {
2159  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2160  caseAction.execute(progressIndicator, additionalParams);
2161  } else {
2162  acquireCaseLock(caseLockType);
2163  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2164  if (null == resourcesLock) {
2165  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2166  }
2167  caseAction.execute(progressIndicator, additionalParams);
2168  } catch (CaseActionException ex) {
2169  releaseCaseLock();
2170  throw ex;
2171  }
2172  }
2173  return null;
2174  });
2175  if (null != cancelButtonListener) {
2176  cancelButtonListener.setCaseActionFuture(future);
2177  }
2178 
2179  /*
2180  * Wait for the case action task to finish.
2181  */
2182  try {
2183  future.get();
2184  } catch (InterruptedException discarded) {
2185  /*
2186  * The thread this method is running in has been interrupted.
2187  */
2188  if (null != cancelButtonListener) {
2189  cancelButtonListener.actionPerformed(null);
2190  } else {
2191  future.cancel(true);
2192  }
2193  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2194  } catch (CancellationException discarded) {
2195  /*
2196  * The case action has been cancelled.
2197  */
2198  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2199  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2200  } catch (ExecutionException ex) {
2201  /*
2202  * The case action has thrown an exception.
2203  */
2204  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2205  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
2206  } finally {
2207  progressIndicator.finish();
2208  }
2209  }
2210 
2226  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2227  assert (additionalParams == null);
2228  try {
2230  createCaseDirectoryIfDoesNotExist(progressIndicator);
2232  switchLoggingToCaseLogsDirectory(progressIndicator);
2234  saveCaseMetadataToFile(progressIndicator);
2236  createCaseNodeData(progressIndicator);
2239  createCaseDatabase(progressIndicator);
2241  openCaseLevelServices(progressIndicator);
2243  openAppServiceCaseResources(progressIndicator, true);
2245  openCommunicationChannels(progressIndicator);
2246  return null;
2247 
2248  } catch (CaseActionException ex) {
2249  /*
2250  * Cancellation or failure. The sleep is a little hack to clear the
2251  * interrupted flag for this thread if this is a cancellation
2252  * scenario, so that the clean up can run to completion in the
2253  * current thread.
2254  */
2255  try {
2256  Thread.sleep(1);
2257  } catch (InterruptedException discarded) {
2258  }
2259  close(progressIndicator);
2260  throw ex;
2261  }
2262  }
2263 
2278  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2279  assert (additionalParams == null);
2280  try {
2282  switchLoggingToCaseLogsDirectory(progressIndicator);
2284  updateCaseNodeData(progressIndicator);
2286  deleteTempfilesFromCaseDirectory(progressIndicator);
2288  openCaseDataBase(progressIndicator);
2290  openCaseLevelServices(progressIndicator);
2292  openAppServiceCaseResources(progressIndicator, false);
2294  openCommunicationChannels(progressIndicator);
2297  return null;
2298 
2299  } catch (CaseActionException ex) {
2300  /*
2301  * Cancellation or failure. The sleep is a little hack to clear the
2302  * interrupted flag for this thread if this is a cancellation
2303  * scenario, so that the clean up can run to completion in the
2304  * current thread.
2305  */
2306  try {
2307  Thread.sleep(1);
2308  } catch (InterruptedException discarded) {
2309  }
2310  close(progressIndicator);
2311  throw ex;
2312  }
2313  }
2314 
2324  @Messages({
2325  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2326  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2327  })
2329  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2330  backgroundOpenFileSystemsFuture.cancel(true);
2331  }
2332 
2334  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2335  }
2336 
2341  private static class BackgroundOpenFileSystemsTask implements Runnable {
2342 
2343  private final SleuthkitCase tskCase;
2344  private final String caseName;
2345  private final long MAX_IMAGE_THRESHOLD = 100;
2347 
2356  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2357  this.tskCase = tskCase;
2358  this.progressIndicator = progressIndicator;
2359  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2360  }
2361 
2369  private void checkIfCancelled() throws InterruptedException {
2370  if (Thread.interrupted()) {
2371  throw new InterruptedException();
2372  }
2373  }
2374 
2380  private List<Image> getImages() {
2381  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2382  try {
2383  return this.tskCase.getImages();
2384  } catch (TskCoreException ex) {
2385  logger.log(
2386  Level.SEVERE,
2387  String.format("Could not obtain images while opening case: %s.", caseName),
2388  ex);
2389 
2390  return null;
2391  }
2392  }
2393 
2403  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2404  byte[] tempBuff = new byte[512];
2405 
2406  for (Image image : images) {
2407  String imageStr = image.getName();
2408 
2409  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2410 
2411  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2412  checkIfCancelled();
2413  for (FileSystem fileSystem : fileSystems) {
2414  fileSystem.read(tempBuff, 0, 512);
2415  checkIfCancelled();
2416  }
2417 
2418  }
2419  }
2420 
2421  @Override
2422  public void run() {
2423  try {
2424  checkIfCancelled();
2425  List<Image> images = getImages();
2426  if (images == null) {
2427  return;
2428  }
2429 
2430  if (images.size() > MAX_IMAGE_THRESHOLD) {
2431  // If we have a large number of images, don't try to preload anything
2432  logger.log(
2433  Level.INFO,
2434  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2435  return;
2436  }
2437 
2438  checkIfCancelled();
2439  openFileSystems(images);
2440  } catch (InterruptedException ex) {
2441  logger.log(
2442  Level.INFO,
2443  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2444  } catch (Exception ex) {
2445  // Exception firewall
2446  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2447  }
2448  }
2449 
2450  }
2451 
2466  @Messages({
2467  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2468  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2469  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2470  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2471  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2472  assert (additionalParams instanceof Long);
2473  open(progressIndicator, null);
2474  try {
2475  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2476  Long dataSourceObjectID = (Long) additionalParams;
2477  try {
2478  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2479  if (dataSource == null) {
2480  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2481  }
2482  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2483  } catch (TskDataException | TskCoreException ex) {
2484  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2485  }
2486  try {
2487  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2488  } catch (KeywordSearchServiceException ex) {
2489  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2490  }
2491  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2492  return null;
2493  } finally {
2494  close(progressIndicator);
2495  releaseCaseLock();
2496  }
2497  }
2498 
2509  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2510 
2511  if (portableCaseFolder.exists()) {
2512  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2513  }
2514  if (!portableCaseFolder.mkdirs()) {
2515  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2516  }
2517 
2518  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2520  try {
2521  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2522  caseName, details, metadata);
2523  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2524  } catch (CaseMetadataException ex) {
2525  throw new TskCoreException("Error creating case metadata", ex);
2526  }
2527 
2528  // Create the Sleuthkit case
2529  SleuthkitCase portableSleuthkitCase;
2530  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2531  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2532 
2533  return portableSleuthkitCase;
2534  }
2535 
2546  if (Thread.currentThread().isInterrupted()) {
2547  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2548  }
2549  }
2550 
2561  @Messages({
2562  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2563  })
2564  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2565  /*
2566  * TODO (JIRA-2180): Always create the case directory as part of the
2567  * case creation process.
2568  */
2569  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2570  if (new File(metadata.getCaseDirectory()).exists() == false) {
2571  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2572  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2573  }
2574  }
2575 
2582  @Messages({
2583  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2584  })
2585  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2586  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2588  }
2589 
2601  @Messages({
2602  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2603  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2604  })
2605  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2606  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2607  try {
2608  this.metadata.writeToFile();
2609  } catch (CaseMetadataException ex) {
2610  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2611  }
2612  }
2613 
2625  @Messages({
2626  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2627  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2628  })
2629  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2631  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2632  try {
2633  CaseNodeData.createCaseNodeData(metadata);
2634  } catch (CaseNodeDataException | InterruptedException ex) {
2635  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2636  }
2637  }
2638  }
2639 
2651  @Messages({
2652  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2653  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2654  })
2655  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2657  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2658  try {
2660  nodeData.setLastAccessDate(new Date());
2661  CaseNodeData.writeCaseNodeData(nodeData);
2662  } catch (CaseNodeDataException | InterruptedException ex) {
2663  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2664  }
2665  }
2666  }
2667 
2673  @Messages({
2674  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2675  })
2676  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2677  /*
2678  * Clear the temp subdirectory of the case directory.
2679  */
2680  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2681  FileUtil.deleteDir(new File(this.getTempDirectory()));
2682  }
2683 
2695  @Messages({
2696  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2697  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2698  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2699  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2700  })
2701  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2702  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2703  try {
2704  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2705  /*
2706  * For single-user cases, the case database is a SQLite database
2707  * with a standard name, physically located in the case
2708  * directory.
2709  */
2710  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2711  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2712  } else {
2713  /*
2714  * For multi-user cases, the case database is a PostgreSQL
2715  * database with a name derived from the case display name,
2716  * physically located on the PostgreSQL database server.
2717  */
2718  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2719  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2720  }
2721  } catch (TskCoreException ex) {
2722  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2723  } catch (UserPreferencesException ex) {
2724  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2725  } catch (CaseMetadataException ex) {
2726  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2727  }
2728  }
2729 
2741  @Messages({
2742  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2743  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2744  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2745  "Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.",
2746  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2747  })
2748  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2749  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2750  try {
2751  String databaseName = metadata.getCaseDatabaseName();
2752 
2753  ContentStreamProvider contentProvider = loadContentProvider(metadata.getContentProviderName());
2754  if (StringUtils.isNotBlank(metadata.getContentProviderName()) && contentProvider == null) {
2755  if (metadata.getContentProviderName().trim().toUpperCase().startsWith(CT_PROVIDER_PREFIX.toUpperCase())) {
2756  new CTIntegrationMissingDialog(WindowManager.getDefault().getMainWindow(), true).showDialog(null);
2757  }
2758  throw new CaseActionException(Bundle.Case_exceptionMessage_contentProviderCouldNotBeFound());
2759  }
2760 
2761  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2762  caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath(), contentProvider);
2764  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory(), contentProvider);
2765  } else {
2766  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2767  }
2769  } catch (TskUnsupportedSchemaVersionException ex) {
2770  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2771  } catch (UserPreferencesException ex) {
2772  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2773  } catch (TskCoreException ex) {
2774  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2775  }
2776  }
2777 
2778 
2788  private static ContentStreamProvider loadContentProvider(String providerName) {
2789  Collection<? extends AutopsyContentProvider> customContentProviders = Lookup.getDefault().lookupAll(AutopsyContentProvider.class);
2790  if (customContentProviders != null) {
2791  for (AutopsyContentProvider customProvider : customContentProviders) {
2792  // ensure the provider matches the name
2793  if (customProvider == null || !StringUtils.equalsIgnoreCase(providerName, customProvider.getName())) {
2794  continue;
2795  }
2796 
2797  ContentStreamProvider contentProvider = customProvider.load();
2798  if (contentProvider != null) {
2799  return contentProvider;
2800  }
2801  }
2802  }
2803 
2804  return null;
2805  }
2806 
2807 
2814  @Messages({
2815  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2816  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2817  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2818  this.caseServices = new Services(caseDb);
2819  /*
2820  * RC Note: JM put this initialization here. I'm not sure why. However,
2821  * my attempt to put it in the openCaseDatabase method seems to lead to
2822  * intermittent unchecked exceptions concerning a missing subscriber.
2823  */
2824  caseDb.registerForEvents(sleuthkitEventListener);
2825  }
2826 
2839  @NbBundle.Messages({
2840  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2841  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2842  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2843  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2844  })
2845  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2846  /*
2847  * Each service gets its own independently cancellable/interruptible
2848  * task, running in a named thread managed by an executor service, with
2849  * its own progress indicator. This allows for cancellation of the
2850  * opening of case resources for individual services. It also makes it
2851  * possible to ensure that each service task completes before the next
2852  * one starts by awaiting termination of the executor service.
2853  */
2854  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2855 
2856  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2857  )) {
2858  /*
2859  * Create a progress indicator for the task and start the task. If
2860  * running with a GUI, the progress indicator will be a dialog box
2861  * with a Cancel button.
2862  */
2863  CancelButtonListener cancelButtonListener = null;
2864  ProgressIndicator appServiceProgressIndicator;
2866  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2867  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2868  mainFrame,
2869  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2870  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2871  Bundle.Case_progressIndicatorCancelButton_label(),
2872  cancelButtonListener);
2873  } else {
2874  appServiceProgressIndicator = new LoggingProgressIndicator();
2875  }
2876  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2877  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2878  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2879  threadNameSuffix = threadNameSuffix.toLowerCase();
2880  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2881  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2882  Future<Void> future = executor.submit(() -> {
2883  service.openCaseResources(context);
2884  return null;
2885  });
2886  if (null != cancelButtonListener) {
2887  cancelButtonListener.setCaseContext(context);
2888  cancelButtonListener.setCaseActionFuture(future);
2889  }
2890 
2891  /*
2892  * Wait for the task to either be completed or
2893  * cancelled/interrupted, or for the opening of the case to be
2894  * cancelled.
2895  */
2896  try {
2897  future.get();
2898  } catch (InterruptedException discarded) {
2899  /*
2900  * The parent create/open case task has been cancelled.
2901  */
2902  Case.logger.log(Level.WARNING, String.format("Opening of %s (%s) in %s cancelled during opening of case resources by %s", getDisplayName(), getName(), getCaseDirectory(), service.getServiceName()));
2903  future.cancel(true);
2904  } catch (CancellationException discarded) {
2905  /*
2906  * The opening of case resources by the application service has
2907  * been cancelled, so the executor service has thrown. Note that
2908  * there is no guarantee the task itself has responded to the
2909  * cancellation request yet.
2910  */
2911  Case.logger.log(Level.WARNING, String.format("Opening of case resources by %s for %s (%s) in %s cancelled", service.getServiceName(), getDisplayName(), getName(), getCaseDirectory(), service.getServiceName()));
2912  } catch (ExecutionException ex) {
2913  /*
2914  * An exception was thrown while executing the task. The
2915  * case-specific application service resources are not
2916  * essential. Log an error and notify the user if running the
2917  * desktop GUI, but do not throw.
2918  */
2919  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2921  SwingUtilities.invokeLater(() -> {
2922  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2923  });
2924  }
2925  } finally {
2926  /*
2927  * Shut down the executor service and wait for it to finish.
2928  * This ensures that the task has finished. Without this, it
2929  * would be possible to start the next task before the current
2930  * task responded to a cancellation request.
2931  */
2933  appServiceProgressIndicator.finish();
2934  }
2936  }
2937  }
2938 
2950  @Messages({
2951  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2952  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2953  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2954  })
2955  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2956  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2957  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2958  try {
2959  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2961  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2962  } catch (AutopsyEventException ex) {
2963  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2964  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2965  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2966  }
2967  }
2968  }
2969 
2984  private void doCloseCaseAction() throws CaseActionException {
2985  /*
2986  * Set up either a GUI progress indicator without a Cancel button or a
2987  * logging progress indicator.
2988  */
2989  ProgressIndicator progressIndicator;
2991  progressIndicator = new ModalDialogProgressIndicator(
2992  mainFrame,
2993  Bundle.Case_progressIndicatorTitle_closingCase());
2994  } else {
2995  progressIndicator = new LoggingProgressIndicator();
2996  }
2997  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2998 
2999  /*
3000  * Closing a case is always done in the same non-UI thread that
3001  * opened/created the case. If the case is a multi-user case, this
3002  * ensures that case lock that is held as long as the case is open is
3003  * released in the same thread in which it was acquired, as is required
3004  * by the coordination service.
3005  */
3006  Future<Void> future = caseActionExecutor.submit(() -> {
3007  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
3008  close(progressIndicator);
3009  } else {
3010  /*
3011  * Acquire an exclusive case resources lock to ensure only one
3012  * node at a time can create/open/upgrade/close the case
3013  * resources.
3014  */
3015  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
3016  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
3017  if (null == resourcesLock) {
3018  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
3019  }
3020  close(progressIndicator);
3021  } finally {
3022  /*
3023  * Always release the case directory lock that was acquired
3024  * when the case was opened.
3025  */
3026  releaseCaseLock();
3027  }
3028  }
3029  return null;
3030  });
3031 
3032  try {
3033  future.get();
3034  } catch (InterruptedException | CancellationException unused) {
3035  /*
3036  * The wait has been interrupted by interrupting the thread running
3037  * this method. Not allowing cancellation of case closing, so ignore
3038  * the interrupt. Likewise, cancellation of the case closing task is
3039  * not supported.
3040  */
3041  } catch (ExecutionException ex) {
3042  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
3043  } finally {
3044  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
3045  progressIndicator.finish();
3046  }
3047  }
3048 
3054  @Messages({
3055  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
3056  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
3057  "Case.progressMessage.closingCaseDatabase=Closing case database..."
3058  })
3059  private void close(ProgressIndicator progressIndicator) {
3061 
3062  /*
3063  * Stop sending/receiving case events to and from other nodes if this is
3064  * a multi-user case.
3065  */
3066  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
3067  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
3068  if (null != collaborationMonitor) {
3069  collaborationMonitor.shutdown();
3070  }
3071  eventPublisher.closeRemoteEventChannel();
3072  }
3073 
3074  /*
3075  * Allow all registered application services providers to close
3076  * resources related to the case.
3077  */
3078  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
3080 
3081  /*
3082  * Close the case database.
3083  */
3084  if (null != caseDb) {
3085  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
3086  caseDb.unregisterForEvents(sleuthkitEventListener);
3087  caseDb.close();
3088  }
3089 
3093  deleteTempfilesFromCaseDirectory(progressIndicator);
3094 
3095  /*
3096  * Switch the log directory.
3097  */
3098  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
3100  }
3101 
3106  @Messages({
3107  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
3108  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
3109  })
3111  /*
3112  * Each service gets its own independently cancellable task, and thus
3113  * its own task progress indicator.
3114  */
3115  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
3116  )) {
3117  ProgressIndicator progressIndicator;
3119  progressIndicator = new ModalDialogProgressIndicator(
3120  mainFrame,
3121  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
3122  } else {
3123  progressIndicator = new LoggingProgressIndicator();
3124  }
3125  progressIndicator.start(Bundle.Case_progressMessage_preparing());
3126  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
3127  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
3128  threadNameSuffix = threadNameSuffix.toLowerCase();
3129  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
3130  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
3131  Future<Void> future = executor.submit(() -> {
3132  service.closeCaseResources(context);
3133  return null;
3134  });
3135  try {
3136  future.get();
3137  } catch (InterruptedException ex) {
3138  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
3139  } catch (CancellationException ex) {
3140  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
3141  } catch (ExecutionException ex) {
3142  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
3144  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
3145  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
3146  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
3147  }
3148  } finally {
3150  progressIndicator.finish();
3151  }
3152  }
3153  }
3154 
3160  @Messages({
3161  "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.",
3162  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case."
3163  })
3164  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
3165  String caseDir = metadata.getCaseDirectory();
3166  try {
3167  CoordinationService coordinationService = CoordinationService.getInstance();
3168  caseLock = lockType == CaseLockType.SHARED
3169  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
3170  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
3171  if (caseLock == null) {
3172  if (lockType == CaseLockType.SHARED) {
3173  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
3174  } else {
3175  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
3176  }
3177  }
3178  } catch (InterruptedException | CoordinationServiceException ex) {
3179  if (lockType == CaseLockType.SHARED) {
3180  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
3181  } else {
3182  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
3183  }
3184  }
3185  }
3186 
3190  private void releaseCaseLock() {
3191  if (caseLock != null) {
3192  try {
3193  caseLock.release();
3194  caseLock = null;
3196  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
3197  }
3198  }
3199  }
3200 
3207  private String getOrCreateSubdirectory(String subDirectoryName) {
3208  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
3209  if (!subDirectory.exists()) {
3210  subDirectory.mkdirs();
3211  }
3212  return subDirectory.toString();
3213 
3214  }
3215 
3227  @Messages({
3228  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
3229  })
3230  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3231  boolean errorsOccurred = false;
3232  try {
3233  deleteTextIndex(metadata, progressIndicator);
3234  } catch (KeywordSearchServiceException ex) {
3235  errorsOccurred = true;
3236  logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3237  }
3238 
3239  try {
3240  deleteCaseDirectory(metadata, progressIndicator);
3241  } catch (CaseActionException ex) {
3242  errorsOccurred = true;
3243  logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3244  }
3245 
3246  deleteFromRecentCases(metadata, progressIndicator);
3247 
3248  if (errorsOccurred) {
3249  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3250  }
3251  }
3252 
3272  @Messages({
3273  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
3274  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
3275  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
3276  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
3277  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
3278  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
3279  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
3280  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
3281  })
3282  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
3283  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
3284  CoordinationService coordinationService;
3285  try {
3286  coordinationService = CoordinationService.getInstance();
3287  } catch (CoordinationServiceException ex) {
3288  logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3289  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
3290  }
3291 
3292  CaseNodeData caseNodeData;
3293  boolean errorsOccurred = false;
3294  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
3295  if (dirLock == null) {
3296  logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); //NON-NLS
3297  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
3298  }
3299 
3300  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
3301  try {
3302  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
3303  } catch (CaseNodeDataException | InterruptedException ex) {
3304  logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3305  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
3306  }
3307 
3308  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
3309 
3310  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
3311  try {
3312  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
3313  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
3314  } catch (CoordinationServiceException ex) {
3315  if (!isNoNodeException(ex)) {
3316  errorsOccurred = true;
3317  logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3318  }
3319  } catch (InterruptedException ex) {
3320  logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3321  }
3322 
3323  } catch (CoordinationServiceException ex) {
3324  logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3325  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
3326  }
3327 
3328  if (!errorsOccurred) {
3329  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3330  try {
3331  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3332  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3333  } catch (CoordinationServiceException | InterruptedException ex) {
3334  logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3335  errorsOccurred = true;
3336  }
3337  }
3338 
3339  if (errorsOccurred) {
3340  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3341  }
3342  }
3343 
3368  @Beta
3369  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3370  boolean errorsOccurred = false;
3371  try {
3372  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3373  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3374  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3375  deleteFromRecentCases(metadata, progressIndicator);
3376  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3377  errorsOccurred = true;
3378  logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3379  } catch (KeywordSearchServiceException ex) {
3380  errorsOccurred = true;
3381  logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3382  } catch (CaseActionException ex) {
3383  errorsOccurred = true;
3384  logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3385  }
3386  return errorsOccurred;
3387  }
3388 
3409  @Messages({
3410  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3411  })
3412  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3413  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3414  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3415  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3416  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3417  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3418  Class.forName("org.postgresql.Driver"); //NON-NLS
3419  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3420  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3421  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3422  if (queryResult.next()) {
3423  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3424  statement.execute(deleteCommand);
3425  }
3426  }
3427  }
3429  }
3430  }
3431 
3447  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3449  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3450  deleteTextIndex(metadata, progressIndicator);
3452  }
3453  }
3454 
3464  @Messages({
3465  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3466  })
3467  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3468  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3469 
3470  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3471  )) {
3472  searchService.deleteTextIndex(metadata);
3473  }
3474  }
3475 
3490  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3491  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3492  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3493  deleteCaseDirectory(metadata, progressIndicator);
3495  }
3496  }
3497 
3507  @Messages({
3508  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3509  })
3510  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3511  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3512  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3513  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3514  }
3515  }
3516 
3524  @Messages({
3525  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3526  })
3527  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3529  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3530  SwingUtilities.invokeLater(() -> {
3531  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3532  });
3533  }
3534  }
3535 
3546  boolean isNodeNodeEx = false;
3547  Throwable cause = ex.getCause();
3548  if (cause != null) {
3549  String causeMessage = cause.getMessage();
3550  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3551  }
3552  return isNodeNodeEx;
3553  }
3554 
3566  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3567  try {
3568  caseNodeData.setDeletedFlag(flag);
3569  CaseNodeData.writeCaseNodeData(caseNodeData);
3570  } catch (CaseNodeDataException ex) {
3571  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);
3572 
3573  }
3574  }
3575 
3587  private void updateDataParameters() throws TskCoreException {
3588  hasDataSource = dbHasDataSource();
3589 
3590  if (!hasDataSource) {
3591  hasData = dbHasData();
3592  } else {
3593  hasData = true;
3594  }
3595  }
3596 
3604  private boolean dbHasDataSource() throws TskCoreException {
3605  String query = "SELECT count(*) AS count FROM (SELECT * FROM data_source_info LIMIT 1)t";
3606  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3607  ResultSet resultSet = dbQuery.getResultSet();
3608  if (resultSet.next()) {
3609  return resultSet.getLong("count") > 0;
3610  }
3611  return false;
3612  } catch (SQLException ex) {
3613  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3614  throw new TskCoreException("Error accessing case databse", ex);
3615  }
3616  }
3617 
3626  private boolean dbHasData() throws TskCoreException {
3627  // The LIMIT 1 in the subquery should limit the data returned and
3628  // make the overall query more efficent.
3629  String query = "SELECT SUM(cnt) total FROM "
3630  + "(SELECT COUNT(*) AS cnt FROM "
3631  + "(SELECT * FROM tsk_objects LIMIT 1)t "
3632  + "UNION ALL "
3633  + "SELECT COUNT(*) AS cnt FROM "
3634  + "(SELECT * FROM tsk_hosts LIMIT 1)r) s";
3635  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3636  ResultSet resultSet = dbQuery.getResultSet();
3637  if (resultSet.next()) {
3638  return resultSet.getLong("total") > 0;
3639  } else {
3640  return false;
3641  }
3642  } catch (SQLException ex) {
3643  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3644  throw new TskCoreException("Error accessing case databse", ex);
3645  }
3646  }
3647 
3656  private interface CaseAction<T, V, R> {
3657 
3668  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3669  }
3670 
3675  private enum CaseLockType {
3676  SHARED, EXCLUSIVE;
3677  }
3678 
3683  @ThreadSafe
3684  private final static class CancelButtonListener implements ActionListener {
3685 
3686  private final String cancellationMessage;
3687  @GuardedBy("this")
3688  private boolean cancelRequested;
3689  @GuardedBy("this")
3691  @GuardedBy("this")
3692  private Future<?> caseActionFuture;
3693 
3702  private CancelButtonListener(String cancellationMessage) {
3703  this.cancellationMessage = cancellationMessage;
3704  }
3705 
3711  private synchronized void setCaseContext(CaseContext caseContext) {
3712  this.caseContext = caseContext;
3713  /*
3714  * If the cancel button has already been pressed, pass the
3715  * cancellation on to the case context.
3716  */
3717  if (cancelRequested) {
3718  cancel();
3719  }
3720  }
3721 
3727  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3728  this.caseActionFuture = caseActionFuture;
3729  /*
3730  * If the cancel button has already been pressed, cancel the Future
3731  * of the task.
3732  */
3733  if (cancelRequested) {
3734  cancel();
3735  }
3736  }
3737 
3743  @Override
3744  public synchronized void actionPerformed(ActionEvent event) {
3745  cancel();
3746  }
3747 
3751  private void cancel() {
3752  /*
3753  * At a minimum, set the cancellation requested flag of this
3754  * listener.
3755  */
3756  this.cancelRequested = true;
3757  if (null != this.caseContext) {
3758  /*
3759  * Set the cancellation request flag and display the
3760  * cancellation message in the progress indicator for the case
3761  * context associated with this listener.
3762  */
3764  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3765  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3766  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3767  }
3768  }
3769  this.caseContext.requestCancel();
3770  }
3771  if (null != this.caseActionFuture) {
3772  /*
3773  * Cancel the Future of the task associated with this listener.
3774  * Note that the task thread will be interrupted if the task is
3775  * blocked.
3776  */
3777  this.caseActionFuture.cancel(true);
3778  }
3779  }
3780  }
3781 
3785  private static class TaskThreadFactory implements ThreadFactory {
3786 
3787  private final String threadName;
3788 
3789  private TaskThreadFactory(String threadName) {
3790  this.threadName = threadName;
3791  }
3792 
3793  @Override
3794  public Thread newThread(Runnable task) {
3795  return new Thread(task, threadName);
3796  }
3797 
3798  }
3799 
3807  @Deprecated
3808  public static String getAppName() {
3809  return UserPreferences.getAppName();
3810  }
3811 
3831  @Deprecated
3832  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3833  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3834  }
3835 
3856  @Deprecated
3857  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3858  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3859  }
3860 
3872  @Deprecated
3873  public static void open(String caseMetadataFilePath) throws CaseActionException {
3874  openAsCurrentCase(caseMetadataFilePath);
3875  }
3876 
3886  @Deprecated
3887  public void closeCase() throws CaseActionException {
3888  closeCurrentCase();
3889  }
3890 
3896  @Deprecated
3897  public static void invokeStartupDialog() {
3899  }
3900 
3914  @Deprecated
3915  public static String convertTimeZone(String timeZoneId) {
3916  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3917  }
3918 
3928  @Deprecated
3929  public static boolean pathExists(String filePath) {
3930  return new File(filePath).isFile();
3931  }
3932 
3941  @Deprecated
3942  public static String getAutopsyVersion() {
3943  return Version.getVersion();
3944  }
3945 
3953  @Deprecated
3954  public static boolean existsCurrentCase() {
3955  return isCaseOpen();
3956  }
3957 
3967  @Deprecated
3968  public static String getModulesOutputDirRelPath() {
3969  return "ModuleOutput"; //NON-NLS
3970  }
3971 
3981  @Deprecated
3982  public static PropertyChangeSupport
3984  return new PropertyChangeSupport(Case.class
3985  );
3986  }
3987 
3996  @Deprecated
3997  public String getModulesOutputDirAbsPath() {
3998  return getModuleDirectory();
3999  }
4000 
4015  @Deprecated
4016  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
4017  try {
4018  Image newDataSource = caseDb.getImageById(imgId);
4019  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
4020  return newDataSource;
4021  } catch (TskCoreException ex) {
4022  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
4023  }
4024  }
4025 
4033  @Deprecated
4034  public Set<TimeZone> getTimeZone() {
4035  return getTimeZones();
4036  }
4037 
4048  @Deprecated
4049  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
4050  deleteReports(reports);
4051  }
4052 
4053 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:183
List< Content > getDataSources()
Definition: Case.java:1709
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1852
Case(CaseMetadata caseMetaData)
Definition: Case.java:2083
static CaseType fromString(String typeName)
Definition: Case.java:231
final SleuthkitEventListener sleuthkitEventListener
Definition: Case.java:194
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:178
static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag)
Definition: Case.java:3566
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:825
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1917
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2278
CoordinationService.Lock caseLock
Definition: Case.java:192
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3369
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:4016
void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event)
Definition: Case.java:537
static synchronized IngestManager getInstance()
void deleteNode(CategoryNode category, String nodePath)
static final ExecutorService openFileSystemsExecutor
Definition: Case.java:187
static final String NO_NODE_ERROR_MSG_FRAGMENT
Definition: Case.java:180
void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event)
Definition: Case.java:614
void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event)
Definition: Case.java:586
void acquireCaseLock(CaseLockType lockType)
Definition: Case.java:3164
static boolean existsCurrentCase()
Definition: Case.java:3954
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:683
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:182
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List< BlackboardArtifactTag > removedTagList)
Definition: Case.java:1906
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:185
static final String EXPORT_FOLDER
Definition: Case.java:173
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:1167
void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event)
Definition: Case.java:552
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1863
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1841
static volatile Frame mainFrame
Definition: Case.java:188
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3915
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3230
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event)
Definition: Case.java:630
void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event)
Definition: Case.java:526
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
volatile ExecutorService caseActionExecutor
Definition: Case.java:191
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1878
static final String APP_NAME
Definition: Case.java:168
static final String CACHE_FOLDER
Definition: Case.java:172
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3490
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:2074
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1932
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1285
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1817
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3857
void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event)
Definition: Case.java:625
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:166
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:170
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:1029
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:4049
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3711
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:167
static boolean isValidName(String caseName)
Definition: Case.java:767
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2748
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2564
CollaborationMonitor collaborationMonitor
Definition: Case.java:195
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2955
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3968
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2605
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3447
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:2124
void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event)
Definition: Case.java:635
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3744
static final String MODULE_FOLDER
Definition: Case.java:177
static final String CT_PROVIDER_PREFIX
Definition: Case.java:181
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2816
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3832
synchronized void openRemoteEventChannel(String channelName)
void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event)
Definition: Case.java:603
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2629
void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event)
Definition: Case.java:640
static final String PLACEHOLDER_DS_PATH_REGEX
Definition: Case.java:198
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:1127
static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3527
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:852
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:743
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static boolean isNoNodeException(CoordinationServiceException ex)
Definition: Case.java:3545
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:3059
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1893
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3983
void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event)
Definition: Case.java:542
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:671
void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event)
Definition: Case.java:575
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3727
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2701
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:733
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2585
void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event)
Definition: Case.java:660
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3467
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2676
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1989
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1804
static ContentStreamProvider loadContentProvider(String providerName)
Definition: Case.java:2788
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1950
static boolean pathExists(String filePath)
Definition: Case.java:3929
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2509
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3873
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:1078
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1259
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:3207
boolean equalsName(String otherTypeName)
Definition: Case.java:289
static final String EVENT_CHANNEL_NAME
Definition: Case.java:171
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event)
Definition: Case.java:531
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:708
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:174
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2226
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:189
void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase)
Definition: Case.java:2845
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1773
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:723
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1828
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:179
void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event)
Definition: Case.java:563
static boolean deleteDir(File dirPath)
Definition: FileUtil.java:47
static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:798
void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event)
Definition: Case.java:645
static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3510
static synchronized DirectoryTreeTopComponent findInstance()
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1789
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3282
static final String CONFIG_FOLDER
Definition: Case.java:176
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:753
static final Object caseActionSerializationLock
Definition: Case.java:184
static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3412
void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event)
Definition: Case.java:650
static final String REPORTS_FOLDER
Definition: Case.java:175
static final String TEMP_FOLDER
Definition: Case.java:169
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:698
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2655
void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event)
Definition: Case.java:655

Copyright © 2012-2022 Basis Technology. Generated on: Tue Feb 6 2024
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.