Autopsy  4.20.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 
22 import com.google.common.annotations.Beta;
23 import com.google.common.eventbus.Subscribe;
24 import com.google.common.util.concurrent.ThreadFactoryBuilder;
25 import java.awt.Cursor;
27 import java.awt.Frame;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.beans.PropertyChangeListener;
31 import java.beans.PropertyChangeSupport;
32 import java.io.File;
33 import java.lang.reflect.InvocationTargetException;
34 import java.nio.file.InvalidPathException;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.sql.Connection;
38 import java.sql.DriverManager;
39 import java.sql.ResultSet;
40 import java.sql.SQLException;
41 import java.sql.Statement;
42 import java.text.SimpleDateFormat;
43 import java.util.Collection;
44 import java.util.Date;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.TimeZone;
51 import java.util.UUID;
52 import java.util.concurrent.CancellationException;
53 import java.util.concurrent.ExecutionException;
54 import java.util.concurrent.ExecutorService;
55 import java.util.concurrent.Executors;
56 import java.util.concurrent.Future;
57 import java.util.concurrent.ThreadFactory;
58 import java.util.concurrent.TimeUnit;
59 import java.util.logging.Level;
60 import java.util.stream.Collectors;
61 import java.util.stream.Stream;
62 import javax.annotation.concurrent.GuardedBy;
63 import javax.annotation.concurrent.ThreadSafe;
64 import javax.swing.JOptionPane;
65 import javax.swing.SwingUtilities;
66 import org.apache.commons.lang3.StringUtils;
67 import org.openide.util.Lookup;
68 import org.openide.util.NbBundle;
69 import org.openide.util.NbBundle.Messages;
70 import org.openide.util.actions.CallableSystemAction;
71 import org.openide.windows.WindowManager;
143 import org.sleuthkit.datamodel.BlackboardArtifactTag;
144 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
145 import org.sleuthkit.datamodel.Content;
146 import org.sleuthkit.datamodel.ContentStreamProvider;
147 import org.sleuthkit.datamodel.ContentTag;
148 import org.sleuthkit.datamodel.DataSource;
149 import org.sleuthkit.datamodel.FileSystem;
150 import org.sleuthkit.datamodel.Image;
151 import org.sleuthkit.datamodel.Report;
152 import org.sleuthkit.datamodel.SleuthkitCase;
153 import org.sleuthkit.datamodel.TimelineManager;
154 import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
155 import org.sleuthkit.datamodel.TskCoreException;
156 import org.sleuthkit.datamodel.TskDataException;
157 import org.sleuthkit.datamodel.TskEvent;
158 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
159 
163 public class Case {
164 
165  private static final int CASE_LOCK_TIMEOUT_MINS = 1;
166  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
167  private static final String APP_NAME = UserPreferences.getAppName();
168  private static final String TEMP_FOLDER = "Temp";
169  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
170  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
171  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
172  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
173  private static final String LOG_FOLDER = "Log"; //NON-NLS
174  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
175  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
176  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
177  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
178  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
179  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
180  private static final Logger logger = Logger.getLogger(Case.class.getName());
182  private static final Object caseActionSerializationLock = new Object();
183  private static Future<?> backgroundOpenFileSystemsFuture = null;
184  private static final ExecutorService openFileSystemsExecutor
185  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
186  private static volatile Frame mainFrame;
187  private static volatile Case currentCase;
188  private final CaseMetadata metadata;
189  private volatile ExecutorService caseActionExecutor;
191  private SleuthkitCase caseDb;
193  private CollaborationMonitor collaborationMonitor;
195 
196  private volatile boolean hasDataSource = false;
197  private volatile boolean hasData = false;
198 
199  /*
200  * Get a reference to the main window of the desktop application to use to
201  * parent pop up dialogs and initialize the application name for use in
202  * changing the main window title.
203  */
204  static {
205  WindowManager.getDefault().invokeWhenUIReady(() -> {
206  mainFrame = WindowManager.getDefault().getMainWindow();
207  });
208  }
209 
213  public enum CaseType {
214 
215  SINGLE_USER_CASE("Single-user case"), //NON-NLS
216  MULTI_USER_CASE("Multi-user case"); //NON-NLS
217 
218  private final String typeName;
219 
227  public static CaseType fromString(String typeName) {
228  if (typeName != null) {
229  for (CaseType c : CaseType.values()) {
230  if (typeName.equalsIgnoreCase(c.toString())) {
231  return c;
232  }
233  }
234  }
235  return null;
236  }
237 
243  @Override
244  public String toString() {
245  return typeName;
246  }
247 
253  @Messages({
254  "Case_caseType_singleUser=Single-user case",
255  "Case_caseType_multiUser=Multi-user case"
256  })
258  if (fromString(typeName) == SINGLE_USER_CASE) {
259  return Bundle.Case_caseType_singleUser();
260  } else {
261  return Bundle.Case_caseType_multiUser();
262  }
263  }
264 
270  private CaseType(String typeName) {
271  this.typeName = typeName;
272  }
273 
284  @Deprecated
285  public boolean equalsName(String otherTypeName) {
286  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
287  }
288 
289  };
290 
295  public enum Events {
296 
304  @Deprecated
313  @Deprecated
322  @Deprecated
433  /*
434  * An item in the central repository has had its comment modified. The
435  * old value is null, the new value is string for current comment.
436  */
486 
491 
496 
501 
506 
511 
512  };
513 
519  private final class SleuthkitEventListener {
520 
521  @Subscribe
522  public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
523  eventPublisher.publish(new TimelineEventAddedEvent(event));
524  }
525 
526  @Subscribe
527  public void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event) {
528  hasData = true;
529  eventPublisher.publish(new OsAccountsAddedEvent(event.getOsAcounts()));
530  }
531 
532  @Subscribe
533  public void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event) {
534  eventPublisher.publish(new OsAccountsUpdatedEvent(event.getOsAcounts()));
535  }
536 
537  @Subscribe
538  public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) {
539  try {
540  hasData = dbHasData();
541  } catch (TskCoreException ex) {
542  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
543  }
544  eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds()));
545  }
546 
547  @Subscribe
548  public void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event) {
549  eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances()));
550  }
551 
558  @Subscribe
559  public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) {
560  hasData = true;
561  eventPublisher.publish(new HostsAddedEvent(event.getHosts()));
562  }
563 
570  @Subscribe
571  public void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event) {
572  eventPublisher.publish(new HostsUpdatedEvent(event.getHosts()));
573  }
574 
581  @Subscribe
582  public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) {
583  try {
584  hasData = dbHasData();
585  } catch (TskCoreException ex) {
586  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
587  }
588 
589  eventPublisher.publish(new HostsDeletedEvent(event.getHostIds()));
590  }
591 
598  @Subscribe
599  public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) {
600  eventPublisher.publish(new PersonsAddedEvent(event.getPersons()));
601  }
602 
609  @Subscribe
610  public void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event) {
611  eventPublisher.publish(new PersonsUpdatedEvent(event.getPersons()));
612  }
613 
620  @Subscribe
621  public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) {
622  eventPublisher.publish(new PersonsDeletedEvent(event.getPersonIds()));
623  }
624 
625  @Subscribe
626  public void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event) {
627  eventPublisher.publish(new HostsAddedToPersonEvent(event.getPerson(), event.getHosts()));
628  }
629 
630  @Subscribe
631  public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) {
632  eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds()));
633  }
634 
635  @Subscribe
636  public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) {
637  eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames()));
638  }
639 
640  @Subscribe
641  public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) {
642  eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames()));
643  }
644 
645  @Subscribe
646  public void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event) {
647  eventPublisher.publish(new TagNamesDeletedEvent(event.getTagNameIds()));
648  }
649 
650  @Subscribe
651  public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) {
652  eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets()));
653  }
654 
655  @Subscribe
656  public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) {
657  eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds()));
658  }
659  }
660 
667  public static void addPropertyChangeListener(PropertyChangeListener listener) {
668  addEventSubscriber(Stream.of(Events.values())
669  .map(Events::toString)
670  .collect(Collectors.toSet()), listener);
671  }
672 
679  public static void removePropertyChangeListener(PropertyChangeListener listener) {
680  removeEventSubscriber(Stream.of(Events.values())
681  .map(Events::toString)
682  .collect(Collectors.toSet()), listener);
683  }
684 
693  @Deprecated
694  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
695  eventPublisher.addSubscriber(eventNames, subscriber);
696  }
697 
704  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
705  eventTypes.forEach((Events event) -> {
706  eventPublisher.addSubscriber(event.toString(), subscriber);
707  });
708  }
709 
718  @Deprecated
719  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
720  eventPublisher.addSubscriber(eventName, subscriber);
721  }
722 
729  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
730  eventPublisher.removeSubscriber(eventName, subscriber);
731  }
732 
739  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
740  eventPublisher.removeSubscriber(eventNames, subscriber);
741  }
742 
749  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
750  eventTypes.forEach((Events event) -> {
751  eventPublisher.removeSubscriber(event.toString(), subscriber);
752  });
753  }
754 
763  public static boolean isValidName(String caseName) {
764  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
765  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
766  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
767  }
768 
793  @Deprecated
794  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
795  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
796  }
797 
817  @Messages({
818  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
819  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
820  })
821  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
822  if (caseDetails.getCaseDisplayName().isEmpty()) {
823  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
824  }
825  if (caseDir.isEmpty()) {
826  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
827  }
828  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
829  }
830 
844  @Messages({
845  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
846  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
847  })
848  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
850  try {
851  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
852  } catch (CaseMetadataException ex) {
853  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
854  }
856  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
857  }
858  openAsCurrentCase(new Case(metadata), false);
859  }
860 
866  public static boolean isCaseOpen() {
867  return currentCase != null;
868  }
869 
877  public static Case getCurrentCase() {
878  try {
879  return getCurrentCaseThrows();
880  } catch (NoCurrentCaseException ex) {
881  /*
882  * Throw a runtime exception, since this is a programming error.
883  */
884  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
885  }
886  }
887 
903  /*
904  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
905  * case method.
906  */
907  Case openCase = currentCase;
908  if (openCase == null) {
909  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
910  } else {
911  return openCase;
912  }
913  }
914 
923  @Messages({
924  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
925  "Case.progressIndicatorTitle.closingCase=Closing Case"
926  })
927  public static void closeCurrentCase() throws CaseActionException {
928  synchronized (caseActionSerializationLock) {
929  if (null == currentCase) {
930  return;
931  }
932  Case closedCase = currentCase;
933  try {
934  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
935  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
936  closedCase.doCloseCaseAction();
937  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
938  } catch (CaseActionException ex) {
939  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
940  throw ex;
941  } finally {
942  currentCase = null;
945  }
946  }
947  }
948  }
949 
958  public static void deleteCurrentCase() throws CaseActionException {
959  synchronized (caseActionSerializationLock) {
960  if (null == currentCase) {
961  return;
962  }
963  CaseMetadata metadata = currentCase.getMetadata();
965  deleteCase(metadata);
966  }
967  }
968 
979  @Messages({
980  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
981  })
982  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
983  synchronized (caseActionSerializationLock) {
984  if (null == currentCase) {
985  return;
986  }
987 
988  /*
989  * Close the current case to release the shared case lock.
990  */
991  CaseMetadata caseMetadata = currentCase.getMetadata();
993 
994  /*
995  * Re-open the case with an exclusive case lock, delete the data
996  * source, and close the case again, releasing the exclusive case
997  * lock.
998  */
999  Case theCase = new Case(caseMetadata);
1000  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
1001 
1002  /*
1003  * Re-open the case with a shared case lock.
1004  */
1005  openAsCurrentCase(new Case(caseMetadata), false);
1006  }
1007  }
1008 
1020  @Messages({
1021  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
1022  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
1023  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
1024  })
1025  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
1026  synchronized (caseActionSerializationLock) {
1027  if (null != currentCase) {
1028  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
1029  }
1030  }
1031 
1032  ProgressIndicator progressIndicator;
1034  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
1035  } else {
1036  progressIndicator = new LoggingProgressIndicator();
1037  }
1038  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1039  try {
1040  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1041  deleteSingleUserCase(metadata, progressIndicator);
1042  } else {
1043  try {
1044  deleteMultiUserCase(metadata, progressIndicator);
1045  } catch (InterruptedException ex) {
1046  /*
1047  * Note that task cancellation is not currently supported
1048  * for this code path, so this catch block is not expected
1049  * to be executed.
1050  */
1051  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
1052  }
1053  }
1054  } finally {
1055  progressIndicator.finish();
1056  }
1057  }
1058 
1069  @Messages({
1070  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1071  "Case.progressIndicatorTitle.openingCase=Opening Case",
1072  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
1073  })
1074  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
1075  synchronized (caseActionSerializationLock) {
1076  if (null != currentCase) {
1077  try {
1078  closeCurrentCase();
1079  } catch (CaseActionException ex) {
1080  /*
1081  * Notify the user and continue (the error has already been
1082  * logged in closeCurrentCase.
1083  */
1084  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
1085  }
1086  }
1087  try {
1088  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1089  String progressIndicatorTitle;
1091  if (isNewCase) {
1092  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
1093  openCaseAction = newCurrentCase::create;
1094  } else {
1095  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
1096  openCaseAction = newCurrentCase::open;
1097  }
1098  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
1099  currentCase = newCurrentCase;
1100  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1102  updateGUIForCaseOpened(newCurrentCase);
1103  }
1104  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
1105  } catch (CaseActionCancelledException ex) {
1106  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
1107  throw ex;
1108  } catch (CaseActionException ex) {
1109  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
1110  throw ex;
1111  }
1112  }
1113  }
1114 
1123  private static String displayNameToUniqueName(String caseDisplayName) {
1124  /*
1125  * Replace all non-ASCII characters.
1126  */
1127  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
1128 
1129  /*
1130  * Replace all control characters.
1131  */
1132  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
1133 
1134  /*
1135  * Replace /, \, :, ?, space, ' ".
1136  */
1137  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
1138 
1139  /*
1140  * Make it all lowercase.
1141  */
1142  uniqueCaseName = uniqueCaseName.toLowerCase();
1143 
1144  /*
1145  * Add a time stamp for uniqueness.
1146  */
1147  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
1148  Date date = new Date();
1149  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
1150 
1151  return uniqueCaseName;
1152  }
1153 
1163  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
1164  /*
1165  * Check the case directory path and permissions. The case directory may
1166  * already exist.
1167  */
1168  File caseDir = new File(caseDirPath);
1169  if (caseDir.exists()) {
1170  if (caseDir.isFile()) {
1171  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
1172  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
1173  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
1174  }
1175  }
1176 
1177  /*
1178  * Create the case directory, if it does not already exist.
1179  */
1180  if (!caseDir.mkdirs()) {
1181  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
1182  }
1183 
1184  /*
1185  * Create the subdirectories of the case directory, if they do not
1186  * already exist. Note that multi-user cases get an extra layer of
1187  * subdirectories, one subdirectory per application host machine.
1188  */
1189  String hostPathComponent = "";
1190  if (caseType == CaseType.MULTI_USER_CASE) {
1191  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
1192  }
1193 
1194  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
1195  if (!exportDir.toFile().mkdirs()) {
1196  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
1197  }
1198 
1199  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
1200  if (!logsDir.toFile().mkdirs()) {
1201  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
1202  }
1203 
1204  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
1205  if (!cacheDir.toFile().mkdirs()) {
1206  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
1207  }
1208 
1209  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
1210  if (!moduleOutputDir.toFile().mkdirs()) {
1211  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
1212  }
1213 
1214  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1215  if (!reportsDir.toFile().mkdirs()) {
1216  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1217  }
1218  }
1219 
1227  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1228  Map<Long, String> imgPaths = new HashMap<>();
1229  try {
1230  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1231  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1232  if (entry.getValue().size() > 0) {
1233  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1234  }
1235  }
1236  } catch (TskCoreException ex) {
1237  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1238  }
1239  return imgPaths;
1240  }
1241 
1252  @Messages({
1253  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1254  })
1255  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1256  try {
1257  Path caseDirPath = Paths.get(caseDir);
1258  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1259  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1260  return lock;
1261  } catch (InterruptedException ex) {
1262  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1263  } catch (CoordinationServiceException ex) {
1264  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1265  }
1266  }
1267 
1268  private static String getNameForTitle() {
1269  //Method should become unnecessary once technical debt story 3334 is done.
1270  if (UserPreferences.getAppName().equals(Version.getName())) {
1271  //Available version number is version number for this application
1272  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1273  } else {
1274  return UserPreferences.getAppName();
1275  }
1276  }
1277 
1281  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1282  /*
1283  * If the case database was upgraded for a new schema and a backup
1284  * database was created, notify the user.
1285  */
1286  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1287  String backupDbPath = caseDb.getBackupDatabasePath();
1288  if (null != backupDbPath) {
1289  JOptionPane.showMessageDialog(
1290  mainFrame,
1291  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1292  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1293  JOptionPane.INFORMATION_MESSAGE);
1294  }
1295 
1296  /*
1297  * Look for the files for the data sources listed in the case database
1298  * and give the user the opportunity to locate any that are missing.
1299  */
1300  Map<Long, String> imgPaths = getImagePaths(caseDb);
1301  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1302  long obj_id = entry.getKey();
1303  String path = entry.getValue();
1304  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1305  if (!fileExists) {
1306  try {
1307  // Using invokeAndWait means that the dialog will
1308  // open on the EDT but this thread will wait for an
1309  // answer. Using invokeLater would cause this loop to
1310  // end before all of the dialogs appeared.
1311  SwingUtilities.invokeAndWait(new Runnable() {
1312  @Override
1313  public void run() {
1314  int response = JOptionPane.showConfirmDialog(
1315  mainFrame,
1316  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1317  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1318  JOptionPane.YES_NO_OPTION);
1319  if (response == JOptionPane.YES_OPTION) {
1320  MissingImageDialog.makeDialog(obj_id, caseDb);
1321  } else {
1322  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1323 
1324  }
1325  }
1326 
1327  });
1328  } catch (InterruptedException | InvocationTargetException ex) {
1329  logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
1330  }
1331  }
1332  }
1333 
1334  /*
1335  * Enable the case-specific actions.
1336  */
1337  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1338  CallableSystemAction.get(OpenHostsAction.class).setEnabled(true);
1339  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1340  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1341  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1342  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1343  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1344  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1345  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1346  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1347  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1348 
1349  /*
1350  * Add the case to the recent cases tracker that supplies a list of
1351  * recent cases to the recent cases menu item and the open/create case
1352  * dialog.
1353  */
1354  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1355  final boolean hasData = newCurrentCase.hasData();
1356 
1357  SwingUtilities.invokeLater(() -> {
1358  /*
1359  * Open the top components (windows within the main application
1360  * window).
1361  *
1362  * Note: If the core windows are not opened here, they will be
1363  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1364  * method on a DATA_SOURCE_ADDED event.
1365  */
1366  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1367  if (hasData) {
1369  } else {
1370  //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible.
1372  }
1373  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
1374 
1375  /*
1376  * Reset the main window title to:
1377  *
1378  * [curent case display name] - [application name].
1379  */
1380  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1381  });
1382  }
1383 
1384  /*
1385  * Update the GUI to to reflect the lack of a current case.
1386  */
1387  private static void updateGUIForCaseClosed() {
1389  SwingUtilities.invokeLater(() -> {
1390  /*
1391  * Close the top components (windows within the main application
1392  * window).
1393  */
1395 
1396  /*
1397  * Disable the case-specific menu items.
1398  */
1399  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1400  CallableSystemAction.get(OpenHostsAction.class).setEnabled(false);
1401  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1402  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1403  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1404  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1405  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1406  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1407  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1408  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1409  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1410 
1411  /*
1412  * Clear the notifications in the notfier component in the lower
1413  * right hand corner of the main application window.
1414  */
1416 
1417  /*
1418  * Reset the main window title to be just the application name,
1419  * instead of [curent case display name] - [application name].
1420  */
1421  mainFrame.setTitle(getNameForTitle());
1422  });
1423  }
1424  }
1425 
1431  public SleuthkitCase getSleuthkitCase() {
1432  return this.caseDb;
1433  }
1434 
1441  return caseServices;
1442  }
1443 
1450  return metadata.getCaseType();
1451  }
1452 
1458  public String getCreatedDate() {
1459  return metadata.getCreatedDate();
1460  }
1461 
1467  public String getName() {
1468  return metadata.getCaseName();
1469  }
1470 
1476  public String getDisplayName() {
1477  return metadata.getCaseDisplayName();
1478  }
1479 
1485  public String getNumber() {
1486  return metadata.getCaseNumber();
1487  }
1488 
1494  public String getExaminer() {
1495  return metadata.getExaminer();
1496  }
1497 
1503  public String getExaminerPhone() {
1504  return metadata.getExaminerPhone();
1505  }
1506 
1512  public String getExaminerEmail() {
1513  return metadata.getExaminerEmail();
1514  }
1515 
1521  public String getCaseNotes() {
1522  return metadata.getCaseNotes();
1523  }
1524 
1530  public String getCaseDirectory() {
1531  return metadata.getCaseDirectory();
1532  }
1533 
1542  public String getOutputDirectory() {
1543  String caseDirectory = getCaseDirectory();
1544  Path hostPath;
1545  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1546  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1547  } else {
1548  hostPath = Paths.get(caseDirectory);
1549  }
1550  if (!hostPath.toFile().exists()) {
1551  hostPath.toFile().mkdirs();
1552  }
1553  return hostPath.toString();
1554  }
1555 
1559  private Path getBaseSystemTempPath() {
1560  return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName());
1561  }
1562 
1569  public String getTempDirectory() {
1570  // NOTE: UserPreferences may also be affected by changes in this method.
1571  // See JIRA-7505 for more information.
1572  Path basePath = null;
1573  // get base temp path for the case based on user preference
1575  case CUSTOM:
1576  String customDirectory = UserMachinePreferences.getCustomTempDirectory();
1577  basePath = (StringUtils.isBlank(customDirectory))
1578  ? null
1579  : Paths.get(customDirectory, APP_NAME, getName());
1580  break;
1581  case CASE:
1582  basePath = Paths.get(getCaseDirectory());
1583  break;
1584  case SYSTEM:
1585  default:
1586  // at this level, if the case directory is specified for a temp
1587  // directory, return the system temp directory instead.
1588  basePath = getBaseSystemTempPath();
1589  break;
1590  }
1591 
1592  basePath = basePath == null ? getBaseSystemTempPath() : basePath;
1593 
1594  // get sub directories based on multi user vs. single user
1595  Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType()))
1596  ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER)
1597  : Paths.get(TEMP_FOLDER);
1598 
1599  File caseTempDir = basePath
1600  .resolve(caseRelPath)
1601  .toFile();
1602 
1603  // ensure directory exists
1604  if (!caseTempDir.exists()) {
1605  caseTempDir.mkdirs();
1606  }
1607 
1608  return caseTempDir.getAbsolutePath();
1609  }
1610 
1617  public String getCacheDirectory() {
1618  return getOrCreateSubdirectory(CACHE_FOLDER);
1619  }
1620 
1627  public String getExportDirectory() {
1628  return getOrCreateSubdirectory(EXPORT_FOLDER);
1629  }
1630 
1637  public String getLogDirectoryPath() {
1638  return getOrCreateSubdirectory(LOG_FOLDER);
1639  }
1640 
1647  public String getReportDirectory() {
1648  return getOrCreateSubdirectory(REPORTS_FOLDER);
1649  }
1650 
1657  public String getConfigDirectory() {
1658  return getOrCreateSubdirectory(CONFIG_FOLDER);
1659  }
1660 
1667  public String getModuleDirectory() {
1668  return getOrCreateSubdirectory(MODULE_FOLDER);
1669  }
1670 
1679  Path path = Paths.get(getModuleDirectory());
1681  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1682  } else {
1683  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1684  }
1685  }
1686 
1696  public List<Content> getDataSources() throws TskCoreException {
1697  return caseDb.getRootObjects();
1698  }
1699 
1705  public Set<TimeZone> getTimeZones() {
1706  Set<TimeZone> timezones = new HashSet<>();
1707  String query = "SELECT time_zone FROM data_source_info";
1708  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1709  ResultSet timeZoneSet = dbQuery.getResultSet();
1710  while (timeZoneSet.next()) {
1711  String timeZone = timeZoneSet.getString("time_zone");
1712  if (timeZone != null && !timeZone.isEmpty()) {
1713  timezones.add(TimeZone.getTimeZone(timeZone));
1714  }
1715  }
1716  } catch (TskCoreException | SQLException ex) {
1717  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1718  }
1719  return timezones;
1720  }
1721 
1728  public String getTextIndexName() {
1729  return getMetadata().getTextIndexName();
1730  }
1731 
1737  public boolean hasData() {
1738  return hasData;
1739  }
1740 
1746  public boolean hasDataSource() {
1747  return hasDataSource;
1748  }
1749 
1760  public void notifyAddingDataSource(UUID eventId) {
1761  hasDataSource = true;
1762  hasData = true;
1763  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1764  }
1765 
1776  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1777  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1778  }
1779 
1791  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1792  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1793  }
1794 
1804  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1805  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1806  }
1807 
1815  public void notifyContentTagAdded(ContentTag newTag) {
1816  notifyContentTagAdded(newTag, null);
1817  }
1818 
1828  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1829  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1830  }
1831 
1839  public void notifyContentTagDeleted(ContentTag deletedTag) {
1840  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1841  }
1842 
1850  public void notifyTagDefinitionChanged(String changedTagName) {
1851  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1852  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1853  }
1854 
1865  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1866  try {
1867  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1868  } catch (NoCurrentCaseException ex) {
1869  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1870  }
1871  }
1872 
1880  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1881  notifyBlackBoardArtifactTagAdded(newTag, null);
1882  }
1883 
1893  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1894  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1895  }
1896 
1904  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1905  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1906  }
1907 
1919  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1920  addReport(localPath, srcModuleName, reportName, null);
1921  }
1922 
1937  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1938  String normalizedLocalPath;
1939  try {
1940  if (localPath.toLowerCase().contains("http:")) {
1941  normalizedLocalPath = localPath;
1942  } else {
1943  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1944  }
1945  } catch (InvalidPathException ex) {
1946  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1947  throw new TskCoreException(errorMsg, ex);
1948  }
1949  hasData = true;
1950 
1951  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1952  eventPublisher.publish(new ReportAddedEvent(report));
1953  return report;
1954  }
1955 
1964  public List<Report> getAllReports() throws TskCoreException {
1965  return this.caseDb.getAllReports();
1966  }
1967 
1976  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1977  for (Report report : reports) {
1978  this.caseDb.deleteReport(report);
1979  }
1980 
1981  try {
1982  hasData = dbHasData();
1983  } catch (TskCoreException ex) {
1984  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
1985  }
1986 
1987  for (Report report : reports) {
1988  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1989  }
1990  }
1991 
1998  return metadata;
1999  }
2000 
2008  @Messages({
2009  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
2010  })
2011  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
2012  CaseDetails oldCaseDetails = metadata.getCaseDetails();
2013  try {
2014  metadata.setCaseDetails(caseDetails);
2015  } catch (CaseMetadataException ex) {
2016  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
2017  }
2018  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2019  try {
2020  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2021  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
2022  CaseNodeData.writeCaseNodeData(nodeData);
2023  } catch (CaseNodeDataException | InterruptedException ex) {
2024  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2025  }
2026  }
2027  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
2028  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
2029  }
2030  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
2031  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
2032  }
2033  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2034  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
2035  }
2036  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
2037  if (RuntimeProperties.runningWithGUI()) {
2038  SwingUtilities.invokeLater(() -> {
2039  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
2040  try {
2041  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
2042  } catch (Exception ex) {
2043  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
2044  }
2045  });
2046  }
2047  }
2048 
2061  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
2062  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
2063  }
2064 
2070  private Case(CaseMetadata caseMetaData) {
2071  metadata = caseMetaData;
2072  sleuthkitEventListener = new SleuthkitEventListener();
2073  }
2074 
2104  @Messages({
2105  "Case.progressIndicatorCancelButton.label=Cancel",
2106  "Case.progressMessage.preparing=Preparing...",
2107  "Case.progressMessage.cancelling=Cancelling...",
2108  "Case.exceptionMessage.cancelled=Cancelled.",
2109  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
2110  })
2111  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
2112  /*
2113  * Create and start either a GUI progress indicator (with or without a
2114  * cancel button) or a logging progress indicator.
2115  */
2116  CancelButtonListener cancelButtonListener = null;
2117  ProgressIndicator progressIndicator;
2119  if (allowCancellation) {
2120  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
2121  progressIndicator = new ModalDialogProgressIndicator(
2122  mainFrame,
2123  progressIndicatorTitle,
2124  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2125  Bundle.Case_progressIndicatorCancelButton_label(),
2126  cancelButtonListener);
2127  } else {
2128  progressIndicator = new ModalDialogProgressIndicator(
2129  mainFrame,
2130  progressIndicatorTitle);
2131  }
2132  } else {
2133  progressIndicator = new LoggingProgressIndicator();
2134  }
2135  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2136 
2137  /*
2138  * Do the case action in the single thread in the case action executor.
2139  * If the case is a multi-user case, a case lock is acquired and held
2140  * until explictly released and an exclusive case resources lock is
2141  * aquired and held for the duration of the action.
2142  */
2143  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
2144  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
2145  Future<Void> future = caseActionExecutor.submit(() -> {
2146  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2147  caseAction.execute(progressIndicator, additionalParams);
2148  } else {
2149  acquireCaseLock(caseLockType);
2150  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2151  if (null == resourcesLock) {
2152  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2153  }
2154  caseAction.execute(progressIndicator, additionalParams);
2155  } catch (CaseActionException ex) {
2156  releaseCaseLock();
2157  throw ex;
2158  }
2159  }
2160  return null;
2161  });
2162  if (null != cancelButtonListener) {
2163  cancelButtonListener.setCaseActionFuture(future);
2164  }
2165 
2166  /*
2167  * Wait for the case action task to finish.
2168  */
2169  try {
2170  future.get();
2171  } catch (InterruptedException discarded) {
2172  /*
2173  * The thread this method is running in has been interrupted.
2174  */
2175  if (null != cancelButtonListener) {
2176  cancelButtonListener.actionPerformed(null);
2177  } else {
2178  future.cancel(true);
2179  }
2180  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2181  } catch (CancellationException discarded) {
2182  /*
2183  * The case action has been cancelled.
2184  */
2185  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2186  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2187  } catch (ExecutionException ex) {
2188  /*
2189  * The case action has thrown an exception.
2190  */
2191  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2192  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
2193  } finally {
2194  progressIndicator.finish();
2195  }
2196  }
2197 
2213  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2214  assert (additionalParams == null);
2215  try {
2217  createCaseDirectoryIfDoesNotExist(progressIndicator);
2219  switchLoggingToCaseLogsDirectory(progressIndicator);
2221  saveCaseMetadataToFile(progressIndicator);
2223  createCaseNodeData(progressIndicator);
2226  createCaseDatabase(progressIndicator);
2228  openCaseLevelServices(progressIndicator);
2230  openAppServiceCaseResources(progressIndicator, true);
2232  openCommunicationChannels(progressIndicator);
2233  return null;
2234 
2235  } catch (CaseActionException ex) {
2236  /*
2237  * Cancellation or failure. The sleep is a little hack to clear the
2238  * interrupted flag for this thread if this is a cancellation
2239  * scenario, so that the clean up can run to completion in the
2240  * current thread.
2241  */
2242  try {
2243  Thread.sleep(1);
2244  } catch (InterruptedException discarded) {
2245  }
2246  close(progressIndicator);
2247  throw ex;
2248  }
2249  }
2250 
2265  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2266  assert (additionalParams == null);
2267  try {
2269  switchLoggingToCaseLogsDirectory(progressIndicator);
2271  updateCaseNodeData(progressIndicator);
2273  deleteTempfilesFromCaseDirectory(progressIndicator);
2275  openCaseDataBase(progressIndicator);
2277  openCaseLevelServices(progressIndicator);
2279  openAppServiceCaseResources(progressIndicator, false);
2281  openCommunicationChannels(progressIndicator);
2284  return null;
2285 
2286  } catch (CaseActionException ex) {
2287  /*
2288  * Cancellation or failure. The sleep is a little hack to clear the
2289  * interrupted flag for this thread if this is a cancellation
2290  * scenario, so that the clean up can run to completion in the
2291  * current thread.
2292  */
2293  try {
2294  Thread.sleep(1);
2295  } catch (InterruptedException discarded) {
2296  }
2297  close(progressIndicator);
2298  throw ex;
2299  }
2300  }
2301 
2311  @Messages({
2312  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2313  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2314  })
2316  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2317  backgroundOpenFileSystemsFuture.cancel(true);
2318  }
2319 
2321  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2322  }
2323 
2328  private static class BackgroundOpenFileSystemsTask implements Runnable {
2329 
2330  private final SleuthkitCase tskCase;
2331  private final String caseName;
2332  private final long MAX_IMAGE_THRESHOLD = 100;
2334 
2343  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2344  this.tskCase = tskCase;
2345  this.progressIndicator = progressIndicator;
2346  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2347  }
2348 
2356  private void checkIfCancelled() throws InterruptedException {
2357  if (Thread.interrupted()) {
2358  throw new InterruptedException();
2359  }
2360  }
2361 
2367  private List<Image> getImages() {
2368  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2369  try {
2370  return this.tskCase.getImages();
2371  } catch (TskCoreException ex) {
2372  logger.log(
2373  Level.SEVERE,
2374  String.format("Could not obtain images while opening case: %s.", caseName),
2375  ex);
2376 
2377  return null;
2378  }
2379  }
2380 
2390  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2391  byte[] tempBuff = new byte[512];
2392 
2393  for (Image image : images) {
2394  String imageStr = image.getName();
2395 
2396  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2397 
2398  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2399  checkIfCancelled();
2400  for (FileSystem fileSystem : fileSystems) {
2401  fileSystem.read(tempBuff, 0, 512);
2402  checkIfCancelled();
2403  }
2404 
2405  }
2406  }
2407 
2408  @Override
2409  public void run() {
2410  try {
2411  checkIfCancelled();
2412  List<Image> images = getImages();
2413  if (images == null) {
2414  return;
2415  }
2416 
2417  if (images.size() > MAX_IMAGE_THRESHOLD) {
2418  // If we have a large number of images, don't try to preload anything
2419  logger.log(
2420  Level.INFO,
2421  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2422  return;
2423  }
2424 
2425  checkIfCancelled();
2426  openFileSystems(images);
2427  } catch (InterruptedException ex) {
2428  logger.log(
2429  Level.INFO,
2430  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2431  } catch (Exception ex) {
2432  // Exception firewall
2433  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2434  }
2435  }
2436 
2437  }
2438 
2453  @Messages({
2454  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2455  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2456  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2457  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2458  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2459  assert (additionalParams instanceof Long);
2460  open(progressIndicator, null);
2461  try {
2462  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2463  Long dataSourceObjectID = (Long) additionalParams;
2464  try {
2465  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2466  if (dataSource == null) {
2467  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2468  }
2469  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2470  } catch (TskDataException | TskCoreException ex) {
2471  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2472  }
2473  try {
2474  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2475  } catch (KeywordSearchServiceException ex) {
2476  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2477  }
2478  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2479  return null;
2480  } finally {
2481  close(progressIndicator);
2482  releaseCaseLock();
2483  }
2484  }
2485 
2496  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2497 
2498  if (portableCaseFolder.exists()) {
2499  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2500  }
2501  if (!portableCaseFolder.mkdirs()) {
2502  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2503  }
2504 
2505  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2507  try {
2508  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2509  caseName, details, metadata);
2510  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2511  } catch (CaseMetadataException ex) {
2512  throw new TskCoreException("Error creating case metadata", ex);
2513  }
2514 
2515  // Create the Sleuthkit case
2516  SleuthkitCase portableSleuthkitCase;
2517  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2518  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2519 
2520  return portableSleuthkitCase;
2521  }
2522 
2533  if (Thread.currentThread().isInterrupted()) {
2534  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2535  }
2536  }
2537 
2548  @Messages({
2549  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2550  })
2551  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2552  /*
2553  * TODO (JIRA-2180): Always create the case directory as part of the
2554  * case creation process.
2555  */
2556  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2557  if (new File(metadata.getCaseDirectory()).exists() == false) {
2558  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2559  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2560  }
2561  }
2562 
2569  @Messages({
2570  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2571  })
2572  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2573  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2575  }
2576 
2588  @Messages({
2589  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2590  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2591  })
2592  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2593  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2594  try {
2595  this.metadata.writeToFile();
2596  } catch (CaseMetadataException ex) {
2597  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2598  }
2599  }
2600 
2612  @Messages({
2613  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2614  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2615  })
2616  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2618  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2619  try {
2620  CaseNodeData.createCaseNodeData(metadata);
2621  } catch (CaseNodeDataException | InterruptedException ex) {
2622  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2623  }
2624  }
2625  }
2626 
2638  @Messages({
2639  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2640  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2641  })
2642  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2644  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2645  try {
2647  nodeData.setLastAccessDate(new Date());
2648  CaseNodeData.writeCaseNodeData(nodeData);
2649  } catch (CaseNodeDataException | InterruptedException ex) {
2650  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2651  }
2652  }
2653  }
2654 
2660  @Messages({
2661  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2662  })
2663  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2664  /*
2665  * Clear the temp subdirectory of the case directory.
2666  */
2667  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2668  FileUtil.deleteDir(new File(this.getTempDirectory()));
2669  }
2670 
2682  @Messages({
2683  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2684  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2685  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2686  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2687  })
2688  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2689  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2690  try {
2691  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2692  /*
2693  * For single-user cases, the case database is a SQLite database
2694  * with a standard name, physically located in the case
2695  * directory.
2696  */
2697  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2698  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2699  } else {
2700  /*
2701  * For multi-user cases, the case database is a PostgreSQL
2702  * database with a name derived from the case display name,
2703  * physically located on the PostgreSQL database server.
2704  */
2705  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2706  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2707  }
2708  } catch (TskCoreException ex) {
2709  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2710  } catch (UserPreferencesException ex) {
2711  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2712  } catch (CaseMetadataException ex) {
2713  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2714  }
2715  }
2716 
2728  @Messages({
2729  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2730  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2731  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2732  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2733  })
2734  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2735  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2736  try {
2737  String databaseName = metadata.getCaseDatabaseName();
2738 
2739  ContentStreamProvider contentProvider = loadContentProvider(metadata.getContentProviderName());
2740 
2741  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2742  // only prefix with metadata directory if databaseName is a relative path
2743  String fullDatabasePath = (new File(databaseName).isAbsolute())
2744  ? databaseName
2745  : Paths.get(metadata.getCaseDirectory(), databaseName).toString();
2746 
2747  caseDb = SleuthkitCase.openCase(fullDatabasePath, contentProvider);
2749  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory(), contentProvider);
2750  } else {
2751  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2752  }
2754  } catch (TskUnsupportedSchemaVersionException ex) {
2755  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2756  } catch (UserPreferencesException ex) {
2757  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2758  } catch (TskCoreException ex) {
2759  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2760  }
2761  }
2762 
2763 
2773  private static ContentStreamProvider loadContentProvider(String providerName) {
2774  Collection<? extends AutopsyContentProvider> customContentProviders = Lookup.getDefault().lookupAll(AutopsyContentProvider.class);
2775  if (customContentProviders != null) {
2776  for (AutopsyContentProvider customProvider : customContentProviders) {
2777  // ensure the provider matches the name
2778  if (customProvider == null || !StringUtils.equalsIgnoreCase(providerName, customProvider.getName())) {
2779  continue;
2780  }
2781 
2782  ContentStreamProvider contentProvider = customProvider.load();
2783  if (contentProvider != null) {
2784  return contentProvider;
2785  }
2786  }
2787  }
2788 
2789  return null;
2790  }
2791 
2792 
2799  @Messages({
2800  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2801  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2802  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2803  this.caseServices = new Services(caseDb);
2804  /*
2805  * RC Note: JM put this initialization here. I'm not sure why. However,
2806  * my attempt to put it in the openCaseDatabase method seems to lead to
2807  * intermittent unchecked exceptions concerning a missing subscriber.
2808  */
2809  caseDb.registerForEvents(sleuthkitEventListener);
2810  }
2811 
2824  @NbBundle.Messages({
2825  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2826  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2827  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2828  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2829  })
2830  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2831  /*
2832  * Each service gets its own independently cancellable/interruptible
2833  * task, running in a named thread managed by an executor service, with
2834  * its own progress indicator. This allows for cancellation of the
2835  * opening of case resources for individual services. It also makes it
2836  * possible to ensure that each service task completes before the next
2837  * one starts by awaiting termination of the executor service.
2838  */
2839  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2840 
2841  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2842  )) {
2843  /*
2844  * Create a progress indicator for the task and start the task. If
2845  * running with a GUI, the progress indicator will be a dialog box
2846  * with a Cancel button.
2847  */
2848  CancelButtonListener cancelButtonListener = null;
2849  ProgressIndicator appServiceProgressIndicator;
2851  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2852  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2853  mainFrame,
2854  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2855  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2856  Bundle.Case_progressIndicatorCancelButton_label(),
2857  cancelButtonListener);
2858  } else {
2859  appServiceProgressIndicator = new LoggingProgressIndicator();
2860  }
2861  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2862  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2863  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2864  threadNameSuffix = threadNameSuffix.toLowerCase();
2865  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2866  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2867  Future<Void> future = executor.submit(() -> {
2868  service.openCaseResources(context);
2869  return null;
2870  });
2871  if (null != cancelButtonListener) {
2872  cancelButtonListener.setCaseContext(context);
2873  cancelButtonListener.setCaseActionFuture(future);
2874  }
2875 
2876  /*
2877  * Wait for the task to either be completed or
2878  * cancelled/interrupted, or for the opening of the case to be
2879  * cancelled.
2880  */
2881  try {
2882  future.get();
2883  } catch (InterruptedException discarded) {
2884  /*
2885  * The parent create/open case task has been cancelled.
2886  */
2887  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()));
2888  future.cancel(true);
2889  } catch (CancellationException discarded) {
2890  /*
2891  * The opening of case resources by the application service has
2892  * been cancelled, so the executor service has thrown. Note that
2893  * there is no guarantee the task itself has responded to the
2894  * cancellation request yet.
2895  */
2896  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()));
2897  } catch (ExecutionException ex) {
2898  /*
2899  * An exception was thrown while executing the task. The
2900  * case-specific application service resources are not
2901  * essential. Log an error and notify the user if running the
2902  * desktop GUI, but do not throw.
2903  */
2904  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2906  SwingUtilities.invokeLater(() -> {
2907  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2908  });
2909  }
2910  } finally {
2911  /*
2912  * Shut down the executor service and wait for it to finish.
2913  * This ensures that the task has finished. Without this, it
2914  * would be possible to start the next task before the current
2915  * task responded to a cancellation request.
2916  */
2918  appServiceProgressIndicator.finish();
2919  }
2921  }
2922  }
2923 
2935  @Messages({
2936  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2937  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2938  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2939  })
2940  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2941  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2942  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2943  try {
2944  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2946  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2947  } catch (AutopsyEventException ex) {
2948  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2949  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2950  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2951  }
2952  }
2953  }
2954 
2969  private void doCloseCaseAction() throws CaseActionException {
2970  /*
2971  * Set up either a GUI progress indicator without a Cancel button or a
2972  * logging progress indicator.
2973  */
2974  ProgressIndicator progressIndicator;
2976  progressIndicator = new ModalDialogProgressIndicator(
2977  mainFrame,
2978  Bundle.Case_progressIndicatorTitle_closingCase());
2979  } else {
2980  progressIndicator = new LoggingProgressIndicator();
2981  }
2982  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2983 
2984  /*
2985  * Closing a case is always done in the same non-UI thread that
2986  * opened/created the case. If the case is a multi-user case, this
2987  * ensures that case lock that is held as long as the case is open is
2988  * released in the same thread in which it was acquired, as is required
2989  * by the coordination service.
2990  */
2991  Future<Void> future = caseActionExecutor.submit(() -> {
2992  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2993  close(progressIndicator);
2994  } else {
2995  /*
2996  * Acquire an exclusive case resources lock to ensure only one
2997  * node at a time can create/open/upgrade/close the case
2998  * resources.
2999  */
3000  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
3001  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
3002  if (null == resourcesLock) {
3003  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
3004  }
3005  close(progressIndicator);
3006  } finally {
3007  /*
3008  * Always release the case directory lock that was acquired
3009  * when the case was opened.
3010  */
3011  releaseCaseLock();
3012  }
3013  }
3014  return null;
3015  });
3016 
3017  try {
3018  future.get();
3019  } catch (InterruptedException | CancellationException unused) {
3020  /*
3021  * The wait has been interrupted by interrupting the thread running
3022  * this method. Not allowing cancellation of case closing, so ignore
3023  * the interrupt. Likewise, cancellation of the case closing task is
3024  * not supported.
3025  */
3026  } catch (ExecutionException ex) {
3027  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
3028  } finally {
3029  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
3030  progressIndicator.finish();
3031  }
3032  }
3033 
3039  @Messages({
3040  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
3041  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
3042  "Case.progressMessage.closingCaseDatabase=Closing case database..."
3043  })
3044  private void close(ProgressIndicator progressIndicator) {
3046 
3047  /*
3048  * Stop sending/receiving case events to and from other nodes if this is
3049  * a multi-user case.
3050  */
3051  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
3052  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
3053  if (null != collaborationMonitor) {
3054  collaborationMonitor.shutdown();
3055  }
3056  eventPublisher.closeRemoteEventChannel();
3057  }
3058 
3059  /*
3060  * Allow all registered application services providers to close
3061  * resources related to the case.
3062  */
3063  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
3065 
3066  /*
3067  * Close the case database.
3068  */
3069  if (null != caseDb) {
3070  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
3071  caseDb.unregisterForEvents(sleuthkitEventListener);
3072  caseDb.close();
3073  }
3074 
3078  deleteTempfilesFromCaseDirectory(progressIndicator);
3079 
3080  /*
3081  * Switch the log directory.
3082  */
3083  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
3085  }
3086 
3091  @Messages({
3092  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
3093  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
3094  })
3096  /*
3097  * Each service gets its own independently cancellable task, and thus
3098  * its own task progress indicator.
3099  */
3100  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
3101  )) {
3102  ProgressIndicator progressIndicator;
3104  progressIndicator = new ModalDialogProgressIndicator(
3105  mainFrame,
3106  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
3107  } else {
3108  progressIndicator = new LoggingProgressIndicator();
3109  }
3110  progressIndicator.start(Bundle.Case_progressMessage_preparing());
3111  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
3112  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
3113  threadNameSuffix = threadNameSuffix.toLowerCase();
3114  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
3115  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
3116  Future<Void> future = executor.submit(() -> {
3117  service.closeCaseResources(context);
3118  return null;
3119  });
3120  try {
3121  future.get();
3122  } catch (InterruptedException ex) {
3123  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
3124  } catch (CancellationException ex) {
3125  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
3126  } catch (ExecutionException ex) {
3127  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
3129  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
3130  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
3131  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
3132  }
3133  } finally {
3135  progressIndicator.finish();
3136  }
3137  }
3138  }
3139 
3145  @Messages({
3146  "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.",
3147  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case."
3148  })
3149  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
3150  String caseDir = metadata.getCaseDirectory();
3151  try {
3152  CoordinationService coordinationService = CoordinationService.getInstance();
3153  caseLock = lockType == CaseLockType.SHARED
3154  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
3155  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
3156  if (caseLock == null) {
3157  if (lockType == CaseLockType.SHARED) {
3158  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
3159  } else {
3160  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
3161  }
3162  }
3163  } catch (InterruptedException | CoordinationServiceException ex) {
3164  if (lockType == CaseLockType.SHARED) {
3165  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
3166  } else {
3167  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
3168  }
3169  }
3170  }
3171 
3175  private void releaseCaseLock() {
3176  if (caseLock != null) {
3177  try {
3178  caseLock.release();
3179  caseLock = null;
3181  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
3182  }
3183  }
3184  }
3185 
3192  private String getOrCreateSubdirectory(String subDirectoryName) {
3193  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
3194  if (!subDirectory.exists()) {
3195  subDirectory.mkdirs();
3196  }
3197  return subDirectory.toString();
3198 
3199  }
3200 
3212  @Messages({
3213  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
3214  })
3215  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3216  boolean errorsOccurred = false;
3217  try {
3218  deleteTextIndex(metadata, progressIndicator);
3219  } catch (KeywordSearchServiceException ex) {
3220  errorsOccurred = true;
3221  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
3222  }
3223 
3224  try {
3225  deleteCaseDirectory(metadata, progressIndicator);
3226  } catch (CaseActionException ex) {
3227  errorsOccurred = true;
3228  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
3229  }
3230 
3231  deleteFromRecentCases(metadata, progressIndicator);
3232 
3233  if (errorsOccurred) {
3234  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3235  }
3236  }
3237 
3257  @Messages({
3258  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
3259  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
3260  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
3261  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
3262  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
3263  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
3264  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
3265  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
3266  })
3267  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
3268  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
3269  CoordinationService coordinationService;
3270  try {
3271  coordinationService = CoordinationService.getInstance();
3272  } catch (CoordinationServiceException ex) {
3273  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
3274  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
3275  }
3276 
3277  CaseNodeData caseNodeData;
3278  boolean errorsOccurred = false;
3279  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
3280  if (dirLock == null) {
3281  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
3282  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
3283  }
3284 
3285  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
3286  try {
3287  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
3288  } catch (CaseNodeDataException | InterruptedException ex) {
3289  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
3290  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
3291  }
3292 
3293  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
3294 
3295  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
3296  try {
3297  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
3298  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
3299  } catch (CoordinationServiceException ex) {
3300  if (!isNoNodeException(ex)) {
3301  errorsOccurred = true;
3302  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
3303  }
3304  } catch (InterruptedException ex) {
3305  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
3306  }
3307 
3308  } catch (CoordinationServiceException ex) {
3309  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
3310  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
3311  }
3312 
3313  if (!errorsOccurred) {
3314  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3315  try {
3316  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3317  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3318  } catch (CoordinationServiceException | InterruptedException ex) {
3319  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
3320  errorsOccurred = true;
3321  }
3322  }
3323 
3324  if (errorsOccurred) {
3325  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3326  }
3327  }
3328 
3353  @Beta
3354  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3355  boolean errorsOccurred = false;
3356  try {
3357  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3358  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3359  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3360  deleteFromRecentCases(metadata, progressIndicator);
3361  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3362  errorsOccurred = true;
3363  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
3364  } catch (KeywordSearchServiceException ex) {
3365  errorsOccurred = true;
3366  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
3367  } catch (CaseActionException ex) {
3368  errorsOccurred = true;
3369  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
3370  }
3371  return errorsOccurred;
3372  }
3373 
3394  @Messages({
3395  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3396  })
3397  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3398  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3399  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3400  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3401  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3402  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3403  Class.forName("org.postgresql.Driver"); //NON-NLS
3404  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3405  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3406  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3407  if (queryResult.next()) {
3408  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3409  statement.execute(deleteCommand);
3410  }
3411  }
3412  }
3414  }
3415  }
3416 
3432  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3434  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3435  deleteTextIndex(metadata, progressIndicator);
3437  }
3438  }
3439 
3449  @Messages({
3450  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3451  })
3452  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3453  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3454 
3455  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3456  )) {
3457  searchService.deleteTextIndex(metadata);
3458  }
3459  }
3460 
3475  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3476  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3477  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3478  deleteCaseDirectory(metadata, progressIndicator);
3480  }
3481  }
3482 
3492  @Messages({
3493  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3494  })
3495  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3496  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3497  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3498  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3499  }
3500  }
3501 
3509  @Messages({
3510  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3511  })
3512  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3514  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3515  SwingUtilities.invokeLater(() -> {
3516  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3517  });
3518  }
3519  }
3520 
3531  boolean isNodeNodeEx = false;
3532  Throwable cause = ex.getCause();
3533  if (cause != null) {
3534  String causeMessage = cause.getMessage();
3535  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3536  }
3537  return isNodeNodeEx;
3538  }
3539 
3551  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3552  try {
3553  caseNodeData.setDeletedFlag(flag);
3554  CaseNodeData.writeCaseNodeData(caseNodeData);
3555  } catch (CaseNodeDataException ex) {
3556  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);
3557 
3558  }
3559  }
3560 
3572  private void updateDataParameters() throws TskCoreException {
3573  hasDataSource = dbHasDataSource();
3574 
3575  if (!hasDataSource) {
3576  hasData = dbHasData();
3577  } else {
3578  hasData = true;
3579  }
3580  }
3581 
3589  private boolean dbHasDataSource() throws TskCoreException {
3590  String query = "SELECT count(*) AS count FROM (SELECT * FROM data_source_info LIMIT 1)t";
3591  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3592  ResultSet resultSet = dbQuery.getResultSet();
3593  if (resultSet.next()) {
3594  return resultSet.getLong("count") > 0;
3595  }
3596  return false;
3597  } catch (SQLException ex) {
3598  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3599  throw new TskCoreException("Error accessing case databse", ex);
3600  }
3601  }
3602 
3611  private boolean dbHasData() throws TskCoreException {
3612  // The LIMIT 1 in the subquery should limit the data returned and
3613  // make the overall query more efficent.
3614  String query = "SELECT SUM(cnt) total FROM "
3615  + "(SELECT COUNT(*) AS cnt FROM "
3616  + "(SELECT * FROM tsk_objects LIMIT 1)t "
3617  + "UNION ALL "
3618  + "SELECT COUNT(*) AS cnt FROM "
3619  + "(SELECT * FROM tsk_hosts LIMIT 1)r) s";
3620  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3621  ResultSet resultSet = dbQuery.getResultSet();
3622  if (resultSet.next()) {
3623  return resultSet.getLong("total") > 0;
3624  } else {
3625  return false;
3626  }
3627  } catch (SQLException ex) {
3628  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3629  throw new TskCoreException("Error accessing case databse", ex);
3630  }
3631  }
3632 
3641  private interface CaseAction<T, V, R> {
3642 
3653  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3654  }
3655 
3660  private enum CaseLockType {
3661  SHARED, EXCLUSIVE;
3662  }
3663 
3668  @ThreadSafe
3669  private final static class CancelButtonListener implements ActionListener {
3670 
3671  private final String cancellationMessage;
3672  @GuardedBy("this")
3673  private boolean cancelRequested;
3674  @GuardedBy("this")
3676  @GuardedBy("this")
3677  private Future<?> caseActionFuture;
3678 
3687  private CancelButtonListener(String cancellationMessage) {
3688  this.cancellationMessage = cancellationMessage;
3689  }
3690 
3696  private synchronized void setCaseContext(CaseContext caseContext) {
3697  this.caseContext = caseContext;
3698  /*
3699  * If the cancel button has already been pressed, pass the
3700  * cancellation on to the case context.
3701  */
3702  if (cancelRequested) {
3703  cancel();
3704  }
3705  }
3706 
3712  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3713  this.caseActionFuture = caseActionFuture;
3714  /*
3715  * If the cancel button has already been pressed, cancel the Future
3716  * of the task.
3717  */
3718  if (cancelRequested) {
3719  cancel();
3720  }
3721  }
3722 
3728  @Override
3729  public synchronized void actionPerformed(ActionEvent event) {
3730  cancel();
3731  }
3732 
3736  private void cancel() {
3737  /*
3738  * At a minimum, set the cancellation requested flag of this
3739  * listener.
3740  */
3741  this.cancelRequested = true;
3742  if (null != this.caseContext) {
3743  /*
3744  * Set the cancellation request flag and display the
3745  * cancellation message in the progress indicator for the case
3746  * context associated with this listener.
3747  */
3749  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3750  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3751  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3752  }
3753  }
3754  this.caseContext.requestCancel();
3755  }
3756  if (null != this.caseActionFuture) {
3757  /*
3758  * Cancel the Future of the task associated with this listener.
3759  * Note that the task thread will be interrupted if the task is
3760  * blocked.
3761  */
3762  this.caseActionFuture.cancel(true);
3763  }
3764  }
3765  }
3766 
3770  private static class TaskThreadFactory implements ThreadFactory {
3771 
3772  private final String threadName;
3773 
3774  private TaskThreadFactory(String threadName) {
3775  this.threadName = threadName;
3776  }
3777 
3778  @Override
3779  public Thread newThread(Runnable task) {
3780  return new Thread(task, threadName);
3781  }
3782 
3783  }
3784 
3792  @Deprecated
3793  public static String getAppName() {
3794  return UserPreferences.getAppName();
3795  }
3796 
3816  @Deprecated
3817  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3818  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3819  }
3820 
3841  @Deprecated
3842  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3843  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3844  }
3845 
3857  @Deprecated
3858  public static void open(String caseMetadataFilePath) throws CaseActionException {
3859  openAsCurrentCase(caseMetadataFilePath);
3860  }
3861 
3871  @Deprecated
3872  public void closeCase() throws CaseActionException {
3873  closeCurrentCase();
3874  }
3875 
3881  @Deprecated
3882  public static void invokeStartupDialog() {
3884  }
3885 
3899  @Deprecated
3900  public static String convertTimeZone(String timeZoneId) {
3901  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3902  }
3903 
3913  @Deprecated
3914  public static boolean pathExists(String filePath) {
3915  return new File(filePath).isFile();
3916  }
3917 
3926  @Deprecated
3927  public static String getAutopsyVersion() {
3928  return Version.getVersion();
3929  }
3930 
3938  @Deprecated
3939  public static boolean existsCurrentCase() {
3940  return isCaseOpen();
3941  }
3942 
3952  @Deprecated
3953  public static String getModulesOutputDirRelPath() {
3954  return "ModuleOutput"; //NON-NLS
3955  }
3956 
3966  @Deprecated
3967  public static PropertyChangeSupport
3969  return new PropertyChangeSupport(Case.class
3970  );
3971  }
3972 
3981  @Deprecated
3982  public String getModulesOutputDirAbsPath() {
3983  return getModuleDirectory();
3984  }
3985 
4000  @Deprecated
4001  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
4002  try {
4003  Image newDataSource = caseDb.getImageById(imgId);
4004  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
4005  return newDataSource;
4006  } catch (TskCoreException ex) {
4007  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
4008  }
4009  }
4010 
4018  @Deprecated
4019  public Set<TimeZone> getTimeZone() {
4020  return getTimeZones();
4021  }
4022 
4033  @Deprecated
4034  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
4035  deleteReports(reports);
4036  }
4037 
4038 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:181
List< Content > getDataSources()
Definition: Case.java:1696
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1839
Case(CaseMetadata caseMetaData)
Definition: Case.java:2070
static CaseType fromString(String typeName)
Definition: Case.java:227
final SleuthkitEventListener sleuthkitEventListener
Definition: Case.java:192
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:177
static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag)
Definition: Case.java:3551
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:821
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1904
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2265
CoordinationService.Lock caseLock
Definition: Case.java:190
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3354
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:4001
void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event)
Definition: Case.java:533
static synchronized IngestManager getInstance()
void deleteNode(CategoryNode category, String nodePath)
static final ExecutorService openFileSystemsExecutor
Definition: Case.java:185
static final String NO_NODE_ERROR_MSG_FRAGMENT
Definition: Case.java:179
void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event)
Definition: Case.java:610
void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event)
Definition: Case.java:582
void acquireCaseLock(CaseLockType lockType)
Definition: Case.java:3149
static boolean existsCurrentCase()
Definition: Case.java:3939
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:679
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:180
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List< BlackboardArtifactTag > removedTagList)
Definition: Case.java:1893
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:183
static final String EXPORT_FOLDER
Definition: Case.java:172
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:1163
void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event)
Definition: Case.java:548
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1850
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1828
static volatile Frame mainFrame
Definition: Case.java:186
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3900
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3215
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event)
Definition: Case.java:626
void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event)
Definition: Case.java:522
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
volatile ExecutorService caseActionExecutor
Definition: Case.java:189
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1865
static final String APP_NAME
Definition: Case.java:167
static final String CACHE_FOLDER
Definition: Case.java:171
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3475
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:2061
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1919
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1281
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1804
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3842
void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event)
Definition: Case.java:621
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:165
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:169
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:1025
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:4034
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3696
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:166
static boolean isValidName(String caseName)
Definition: Case.java:763
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2734
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2551
CollaborationMonitor collaborationMonitor
Definition: Case.java:193
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2940
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3953
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2592
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3432
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:2111
void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event)
Definition: Case.java:631
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3729
static final String MODULE_FOLDER
Definition: Case.java:176
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2801
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3817
synchronized void openRemoteEventChannel(String channelName)
void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event)
Definition: Case.java:599
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2616
void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event)
Definition: Case.java:636
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:1123
static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3512
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:848
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:739
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static boolean isNoNodeException(CoordinationServiceException ex)
Definition: Case.java:3530
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:3044
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1880
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3968
void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event)
Definition: Case.java:538
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:667
void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event)
Definition: Case.java:571
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3712
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2688
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:729
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2572
void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event)
Definition: Case.java:656
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3452
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2663
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1976
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1791
static ContentStreamProvider loadContentProvider(String providerName)
Definition: Case.java:2773
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1937
static boolean pathExists(String filePath)
Definition: Case.java:3914
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2496
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3858
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:1074
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1255
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:3192
boolean equalsName(String otherTypeName)
Definition: Case.java:285
static final String EVENT_CHANNEL_NAME
Definition: Case.java:170
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event)
Definition: Case.java:527
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:704
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:173
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2213
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:187
void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase)
Definition: Case.java:2830
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1760
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:719
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1815
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:178
void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event)
Definition: Case.java:559
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:794
void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event)
Definition: Case.java:641
static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3495
static synchronized DirectoryTreeTopComponent findInstance()
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1776
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3267
static final String CONFIG_FOLDER
Definition: Case.java:175
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:749
static final Object caseActionSerializationLock
Definition: Case.java:182
static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3397
void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event)
Definition: Case.java:646
static final String REPORTS_FOLDER
Definition: Case.java:174
static final String TEMP_FOLDER
Definition: Case.java:168
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:694
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2642
void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event)
Definition: Case.java:651

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