Autopsy  4.21.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Case.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-2021 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.casemodule;
20 
23 import com.google.common.annotations.Beta;
24 import com.google.common.eventbus.Subscribe;
25 import com.google.common.util.concurrent.ThreadFactoryBuilder;
26 import java.awt.Cursor;
28 import java.awt.Frame;
29 import java.awt.GraphicsEnvironment;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.beans.PropertyChangeListener;
33 import java.beans.PropertyChangeSupport;
34 import java.io.File;
35 import java.lang.reflect.InvocationTargetException;
36 import java.nio.file.InvalidPathException;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.sql.Connection;
40 import java.sql.DriverManager;
41 import java.sql.ResultSet;
42 import java.sql.SQLException;
43 import java.sql.Statement;
44 import java.text.SimpleDateFormat;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.Date;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Set;
53 import java.util.TimeZone;
54 import java.util.UUID;
55 import java.util.concurrent.CancellationException;
56 import java.util.concurrent.ExecutionException;
57 import java.util.concurrent.ExecutorService;
58 import java.util.concurrent.Executors;
59 import java.util.concurrent.Future;
60 import java.util.concurrent.ThreadFactory;
61 import java.util.concurrent.TimeUnit;
62 import java.util.logging.Level;
63 import java.util.stream.Collectors;
64 import java.util.stream.Stream;
65 import javax.annotation.concurrent.GuardedBy;
66 import javax.annotation.concurrent.ThreadSafe;
67 import javax.swing.JOptionPane;
68 import javax.swing.SwingUtilities;
69 import org.apache.commons.lang3.ArrayUtils;
70 import org.apache.commons.lang3.StringUtils;
71 import org.openide.util.Lookup;
72 import org.openide.util.NbBundle;
73 import org.openide.util.NbBundle.Messages;
74 import org.openide.util.actions.CallableSystemAction;
75 import org.openide.windows.WindowManager;
164 
168 public class Case {
169 
170  private static final int CASE_LOCK_TIMEOUT_MINS = 1;
171  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
172  private static final String APP_NAME = UserPreferences.getAppName();
173  private static final String TEMP_FOLDER = "Temp";
174  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
175  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
176  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
177  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
178  private static final String LOG_FOLDER = "Log"; //NON-NLS
179  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
180  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
181  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
182  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
183  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
184  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
185  private static final String CT_PROVIDER_PREFIX = "CTStandardContentProvider_";
186  private static final Logger logger = Logger.getLogger(Case.class.getName());
188  private static final Object caseActionSerializationLock = new Object();
189  private static Future<?> backgroundOpenFileSystemsFuture = null;
190  private static final ExecutorService openFileSystemsExecutor
191  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
192  private static volatile Frame mainFrame;
193  private static volatile Case currentCase;
194  private final CaseMetadata metadata;
195  private volatile ExecutorService caseActionExecutor;
199  private CollaborationMonitor collaborationMonitor;
201 
202  private volatile boolean hasDataSource = false;
203  private volatile boolean hasData = false;
204 
205  /*
206  * Get a reference to the main window of the desktop application to use to
207  * parent pop up dialogs and initialize the application name for use in
208  * changing the main window title.
209  */
210  static {
211  if (!GraphicsEnvironment.isHeadless()) {
212  WindowManager.getDefault().invokeWhenUIReady(() -> {
213  mainFrame = WindowManager.getDefault().getMainWindow();
214  });
215  }
216  }
217 
221  public enum CaseType {
222 
223  SINGLE_USER_CASE("Single-user case"), //NON-NLS
224  MULTI_USER_CASE("Multi-user case"); //NON-NLS
225 
226  private final String typeName;
227 
235  public static CaseType fromString(String typeName) {
236  if (typeName != null) {
237  for (CaseType c : CaseType.values()) {
238  if (typeName.equalsIgnoreCase(c.toString())) {
239  return c;
240  }
241  }
242  }
243  return null;
244  }
245 
251  @Override
252  public String toString() {
253  return typeName;
254  }
255 
261  @Messages({
262  "Case_caseType_singleUser=Single-user case",
263  "Case_caseType_multiUser=Multi-user case"
264  })
266  if (fromString(typeName) == SINGLE_USER_CASE) {
267  return Bundle.Case_caseType_singleUser();
268  } else {
269  return Bundle.Case_caseType_multiUser();
270  }
271  }
272 
278  private CaseType(String typeName) {
279  this.typeName = typeName;
280  }
281 
292  @Deprecated
293  public boolean equalsName(String otherTypeName) {
294  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
295  }
296 
297  };
298 
303  public enum Events {
304 
312  @Deprecated
321  @Deprecated
330  @Deprecated
441  /*
442  * An item in the central repository has had its comment modified. The
443  * old value is null, the new value is string for current comment.
444  */
494 
499 
504 
509 
514 
519 
520  };
521 
527  private final class SleuthkitEventListener {
528 
529  @Subscribe
531  eventPublisher.publish(new TimelineEventAddedEvent(event));
532  }
533 
534  @Subscribe
536  hasData = true;
537  eventPublisher.publish(new OsAccountsAddedEvent(event.getOsAcounts()));
538  }
539 
540  @Subscribe
542  eventPublisher.publish(new OsAccountsUpdatedEvent(event.getOsAcounts()));
543  }
544 
545  @Subscribe
547  try {
548  hasData = dbHasData();
549  } catch (TskCoreException ex) {
550  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
551  }
552  eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds()));
553  }
554 
555  @Subscribe
557  eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances()));
558  }
559 
566  @Subscribe
568  hasData = true;
569  eventPublisher.publish(new HostsAddedEvent(event.getHosts()));
570  }
571 
578  @Subscribe
580  eventPublisher.publish(new HostsUpdatedEvent(event.getHosts()));
581  }
582 
589  @Subscribe
591  try {
592  hasData = dbHasData();
593  } catch (TskCoreException ex) {
594  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
595  }
596 
597  eventPublisher.publish(new HostsDeletedEvent(event.getHostIds()));
598  }
599 
606  @Subscribe
608  eventPublisher.publish(new PersonsAddedEvent(event.getPersons()));
609  }
610 
617  @Subscribe
619  eventPublisher.publish(new PersonsUpdatedEvent(event.getPersons()));
620  }
621 
628  @Subscribe
630  eventPublisher.publish(new PersonsDeletedEvent(event.getPersonIds()));
631  }
632 
633  @Subscribe
635  eventPublisher.publish(new HostsAddedToPersonEvent(event.getPerson(), event.getHosts()));
636  }
637 
638  @Subscribe
640  eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds()));
641  }
642 
643  @Subscribe
645  eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames()));
646  }
647 
648  @Subscribe
650  eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames()));
651  }
652 
653  @Subscribe
655  eventPublisher.publish(new TagNamesDeletedEvent(event.getTagNameIds()));
656  }
657 
658  @Subscribe
660  eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets()));
661  }
662 
663  @Subscribe
665  eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds()));
666  }
667  }
668 
675  public static void addPropertyChangeListener(PropertyChangeListener listener) {
676  addEventSubscriber(Stream.of(Events.values())
677  .map(Events::toString)
678  .collect(Collectors.toSet()), listener);
679  }
680 
687  public static void removePropertyChangeListener(PropertyChangeListener listener) {
688  removeEventSubscriber(Stream.of(Events.values())
689  .map(Events::toString)
690  .collect(Collectors.toSet()), listener);
691  }
692 
701  @Deprecated
702  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
703  eventPublisher.addSubscriber(eventNames, subscriber);
704  }
705 
712  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
713  eventTypes.forEach((Events event) -> {
714  eventPublisher.addSubscriber(event.toString(), subscriber);
715  });
716  }
717 
726  @Deprecated
727  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
728  eventPublisher.addSubscriber(eventName, subscriber);
729  }
730 
737  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
738  eventPublisher.removeSubscriber(eventName, subscriber);
739  }
740 
747  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
748  eventPublisher.removeSubscriber(eventNames, subscriber);
749  }
750 
757  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
758  eventTypes.forEach((Events event) -> {
759  eventPublisher.removeSubscriber(event.toString(), subscriber);
760  });
761  }
762 
771  public static boolean isValidName(String caseName) {
772  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
773  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
774  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
775  }
776 
801  @Deprecated
802  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
803  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
804  }
805 
825  @Messages({
826  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
827  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
828  })
829  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
830  if (caseDetails.getCaseDisplayName().isEmpty()) {
831  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
832  }
833  if (caseDir.isEmpty()) {
834  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
835  }
836  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
837  }
838 
852  @Messages({
853  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
854  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
855  })
856  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
858  try {
859  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
860  } catch (CaseMetadataException ex) {
861  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
862  }
864  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
865  }
866  openAsCurrentCase(new Case(metadata), false);
867  }
868 
874  public static boolean isCaseOpen() {
875  return currentCase != null;
876  }
877 
885  public static Case getCurrentCase() {
886  try {
887  return getCurrentCaseThrows();
888  } catch (NoCurrentCaseException ex) {
889  /*
890  * Throw a runtime exception, since this is a programming error.
891  */
892  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
893  }
894  }
895 
911  /*
912  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
913  * case method.
914  */
915  Case openCase = currentCase;
916  if (openCase == null) {
917  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
918  } else {
919  return openCase;
920  }
921  }
922 
931  @Messages({
932  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
933  "Case.progressIndicatorTitle.closingCase=Closing Case"
934  })
935  public static void closeCurrentCase() throws CaseActionException {
936  synchronized (caseActionSerializationLock) {
937  if (null == currentCase) {
938  return;
939  }
940  Case closedCase = currentCase;
941  try {
942  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
943  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
944  closedCase.doCloseCaseAction();
945  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
946  } catch (CaseActionException ex) {
947  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
948  throw ex;
949  } finally {
950  currentCase = null;
953  }
954  }
955  }
956  }
957 
966  public static void deleteCurrentCase() throws CaseActionException {
967  synchronized (caseActionSerializationLock) {
968  if (null == currentCase) {
969  return;
970  }
971  CaseMetadata metadata = currentCase.getMetadata();
973  deleteCase(metadata);
974  }
975  }
976 
987  @Messages({
988  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
989  })
990  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
991  synchronized (caseActionSerializationLock) {
992  if (null == currentCase) {
993  return;
994  }
995 
996  /*
997  * Close the current case to release the shared case lock.
998  */
999  CaseMetadata caseMetadata = currentCase.getMetadata();
1000  closeCurrentCase();
1001 
1002  /*
1003  * Re-open the case with an exclusive case lock, delete the data
1004  * source, and close the case again, releasing the exclusive case
1005  * lock.
1006  */
1007  Case theCase = new Case(caseMetadata);
1008  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
1009 
1010  /*
1011  * Re-open the case with a shared case lock.
1012  */
1013  openAsCurrentCase(new Case(caseMetadata), false);
1014  }
1015  }
1016 
1028  @Messages({
1029  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
1030  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
1031  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
1032  })
1033  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
1034  synchronized (caseActionSerializationLock) {
1035  if (null != currentCase) {
1036  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
1037  }
1038  }
1039 
1040  ProgressIndicator progressIndicator;
1042  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
1043  } else {
1044  progressIndicator = new LoggingProgressIndicator();
1045  }
1046  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1047  try {
1048  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1049  deleteSingleUserCase(metadata, progressIndicator);
1050  } else {
1051  try {
1052  deleteMultiUserCase(metadata, progressIndicator);
1053  } catch (InterruptedException ex) {
1054  /*
1055  * Note that task cancellation is not currently supported
1056  * for this code path, so this catch block is not expected
1057  * to be executed.
1058  */
1059  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
1060  }
1061  }
1062  } finally {
1063  progressIndicator.finish();
1064  }
1065  }
1066 
1077  @Messages({
1078  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1079  "Case.progressIndicatorTitle.openingCase=Opening Case",
1080  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
1081  })
1082  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
1083  synchronized (caseActionSerializationLock) {
1084  if (null != currentCase) {
1085  try {
1086  closeCurrentCase();
1087  } catch (CaseActionException ex) {
1088  /*
1089  * Notify the user and continue (the error has already been
1090  * logged in closeCurrentCase.
1091  */
1092  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
1093  }
1094  }
1095  try {
1096  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1097  String progressIndicatorTitle;
1099  if (isNewCase) {
1100  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
1101  openCaseAction = newCurrentCase::create;
1102  } else {
1103  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
1104  openCaseAction = newCurrentCase::open;
1105  }
1106  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
1107  currentCase = newCurrentCase;
1108  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1110  updateGUIForCaseOpened(newCurrentCase);
1111  }
1112  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
1113  } catch (CaseActionCancelledException ex) {
1114  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
1115  throw ex;
1116  } catch (CaseActionException ex) {
1117  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
1118  throw ex;
1119  }
1120  }
1121  }
1122 
1131  private static String displayNameToUniqueName(String caseDisplayName) {
1132  /*
1133  * Replace all non-ASCII characters.
1134  */
1135  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
1136 
1137  /*
1138  * Replace all control characters.
1139  */
1140  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
1141 
1142  /*
1143  * Replace /, \, :, ?, space, ' ".
1144  */
1145  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
1146 
1147  /*
1148  * Make it all lowercase.
1149  */
1150  uniqueCaseName = uniqueCaseName.toLowerCase();
1151 
1152  /*
1153  * Add a time stamp for uniqueness.
1154  */
1155  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
1156  Date date = new Date();
1157  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
1158 
1159  return uniqueCaseName;
1160  }
1161 
1171  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
1172  /*
1173  * Check the case directory path and permissions. The case directory may
1174  * already exist.
1175  */
1176  File caseDir = new File(caseDirPath);
1177  if (caseDir.exists()) {
1178  if (caseDir.isFile()) {
1179  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
1180  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
1181  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
1182  }
1183  }
1184 
1185  /*
1186  * Create the case directory, if it does not already exist.
1187  */
1188  if (!caseDir.mkdirs()) {
1189  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
1190  }
1191 
1192  /*
1193  * Create the subdirectories of the case directory, if they do not
1194  * already exist. Note that multi-user cases get an extra layer of
1195  * subdirectories, one subdirectory per application host machine.
1196  */
1197  String hostPathComponent = "";
1198  if (caseType == CaseType.MULTI_USER_CASE) {
1199  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
1200  }
1201 
1202  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
1203  if (!exportDir.toFile().mkdirs()) {
1204  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
1205  }
1206 
1207  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
1208  if (!logsDir.toFile().mkdirs()) {
1209  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
1210  }
1211 
1212  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
1213  if (!cacheDir.toFile().mkdirs()) {
1214  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
1215  }
1216 
1217  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
1218  if (!moduleOutputDir.toFile().mkdirs()) {
1219  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
1220  }
1221 
1222  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1223  if (!reportsDir.toFile().mkdirs()) {
1224  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1225  }
1226  }
1227 
1235  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1236  Map<Long, String> imgPaths = new HashMap<>();
1237  try {
1238  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1239  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1240  if (entry.getValue().size() > 0) {
1241  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1242  }
1243  }
1244  } catch (TskCoreException ex) {
1245  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1246  }
1247  return imgPaths;
1248  }
1249 
1260  @Messages({
1261  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1262  })
1263  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1264  try {
1265  Path caseDirPath = Paths.get(caseDir);
1266  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1267  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1268  return lock;
1269  } catch (InterruptedException ex) {
1270  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1271  } catch (CoordinationServiceException ex) {
1272  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1273  }
1274  }
1275 
1276  private static String getNameForTitle() {
1277  //Method should become unnecessary once technical debt story 3334 is done.
1278  if (UserPreferences.getAppName().equals(Version.getName())) {
1279  //Available version number is version number for this application
1280  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1281  } else {
1282  return UserPreferences.getAppName();
1283  }
1284  }
1285 
1289  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1290  /*
1291  * If the case database was upgraded for a new schema and a backup
1292  * database was created, notify the user.
1293  */
1294  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1295  String backupDbPath = caseDb.getBackupDatabasePath();
1296  if (null != backupDbPath) {
1297  JOptionPane.showMessageDialog(
1298  mainFrame,
1299  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1300  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1301  JOptionPane.INFORMATION_MESSAGE);
1302  }
1303 
1304  /*
1305  * Look for the files for the data sources listed in the case database
1306  * and give the user the opportunity to locate any that are missing.
1307  */
1308  Map<Long, String> imgPaths = getImagePaths(caseDb);
1309  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1310  long obj_id = entry.getKey();
1311  String path = entry.getValue();
1312  boolean fileExists = (new File(path).exists()|| DriveUtils.driveExists(path));
1313  if (!fileExists) {
1314  try {
1315  DataSource ds = newCurrentCase.getSleuthkitCase().getDataSource(obj_id);
1316  String hostName = StringUtils.defaultString(ds.getHost() == null ? "" : ds.getHost().getName());
1317  // Using invokeAndWait means that the dialog will
1318  // open on the EDT but this thread will wait for an
1319  // answer. Using invokeLater would cause this loop to
1320  // end before all of the dialogs appeared.
1321  SwingUtilities.invokeAndWait(new Runnable() {
1322  @Override
1323  public void run() {
1324  int response = JOptionPane.showConfirmDialog(
1325  mainFrame,
1326  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", hostName, path),
1327  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1328  JOptionPane.YES_NO_OPTION);
1329  if (response == JOptionPane.YES_OPTION) {
1330  MissingImageDialog.makeDialog(obj_id, caseDb);
1331  } else {
1332  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1333 
1334  }
1335  }
1336 
1337  });
1338  } catch (InterruptedException | InvocationTargetException | TskCoreException | TskDataException ex) {
1339  logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
1340  }
1341  }
1342  }
1343 
1344  /*
1345  * Enable the case-specific actions.
1346  */
1347  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1348  CallableSystemAction.get(OpenHostsAction.class).setEnabled(true);
1349  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1350  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1351  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1352  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1353  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1354  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1355  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1356  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1357  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1358 
1359  /*
1360  * Add the case to the recent cases tracker that supplies a list of
1361  * recent cases to the recent cases menu item and the open/create case
1362  * dialog.
1363  */
1364  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1365  final boolean hasData = newCurrentCase.hasData();
1366 
1367  SwingUtilities.invokeLater(() -> {
1368  /*
1369  * Open the top components (windows within the main application
1370  * window).
1371  *
1372  * Note: If the core windows are not opened here, they will be
1373  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1374  * method on a DATA_SOURCE_ADDED event.
1375  */
1376  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1377  if (hasData) {
1379  } else {
1380  //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible.
1382  }
1383  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
1384 
1385  /*
1386  * Reset the main window title to:
1387  *
1388  * [curent case display name] - [application name].
1389  */
1390  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1391  });
1392  }
1393 
1394  /*
1395  * Update the GUI to to reflect the lack of a current case.
1396  */
1397  private static void updateGUIForCaseClosed() {
1399  SwingUtilities.invokeLater(() -> {
1400  /*
1401  * Close the top components (windows within the main application
1402  * window).
1403  */
1405 
1406  /*
1407  * Disable the case-specific menu items.
1408  */
1409  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1410  CallableSystemAction.get(OpenHostsAction.class).setEnabled(false);
1411  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1412  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1413  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1414  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1415  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1416  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1417  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1418  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1419  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1420 
1421  /*
1422  * Clear the notifications in the notfier component in the lower
1423  * right hand corner of the main application window.
1424  */
1426 
1427  /*
1428  * Reset the main window title to be just the application name,
1429  * instead of [curent case display name] - [application name].
1430  */
1431  mainFrame.setTitle(getNameForTitle());
1432  });
1433  }
1434  }
1435 
1442  return this.caseDb;
1443  }
1444 
1451  return caseServices;
1452  }
1453 
1460  return metadata.getCaseType();
1461  }
1462 
1468  public String getCreatedDate() {
1469  return metadata.getCreatedDate();
1470  }
1471 
1477  public String getName() {
1478  return metadata.getCaseName();
1479  }
1480 
1486  public String getDisplayName() {
1487  return metadata.getCaseDisplayName();
1488  }
1489 
1495  public String getNumber() {
1496  return metadata.getCaseNumber();
1497  }
1498 
1504  public String getExaminer() {
1505  return metadata.getExaminer();
1506  }
1507 
1513  public String getExaminerPhone() {
1514  return metadata.getExaminerPhone();
1515  }
1516 
1522  public String getExaminerEmail() {
1523  return metadata.getExaminerEmail();
1524  }
1525 
1531  public String getCaseNotes() {
1532  return metadata.getCaseNotes();
1533  }
1534 
1540  public String getCaseDirectory() {
1541  return metadata.getCaseDirectory();
1542  }
1543 
1552  public String getOutputDirectory() {
1553  String caseDirectory = getCaseDirectory();
1554  Path hostPath;
1555  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1556  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1557  } else {
1558  hostPath = Paths.get(caseDirectory);
1559  }
1560  if (!hostPath.toFile().exists()) {
1561  hostPath.toFile().mkdirs();
1562  }
1563  return hostPath.toString();
1564  }
1565 
1569  private Path getBaseSystemTempPath() {
1570  return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName());
1571  }
1572 
1579  public String getTempDirectory() {
1580  // NOTE: UserPreferences may also be affected by changes in this method.
1581  // See JIRA-7505 for more information.
1582  Path basePath = null;
1583  // get base temp path for the case based on user preference
1585  case CUSTOM:
1586  String customDirectory = UserMachinePreferences.getCustomTempDirectory();
1587  basePath = (StringUtils.isBlank(customDirectory))
1588  ? null
1589  : Paths.get(customDirectory, APP_NAME, getName());
1590  break;
1591  case CASE:
1592  basePath = Paths.get(getCaseDirectory());
1593  break;
1594  case SYSTEM:
1595  default:
1596  // at this level, if the case directory is specified for a temp
1597  // directory, return the system temp directory instead.
1598  basePath = getBaseSystemTempPath();
1599  break;
1600  }
1601 
1602  basePath = basePath == null ? getBaseSystemTempPath() : basePath;
1603 
1604  // get sub directories based on multi user vs. single user
1605  Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType()))
1606  ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER)
1607  : Paths.get(TEMP_FOLDER);
1608 
1609  File caseTempDir = basePath
1610  .resolve(caseRelPath)
1611  .toFile();
1612 
1613  // ensure directory exists
1614  if (!caseTempDir.exists()) {
1615  caseTempDir.mkdirs();
1616  }
1617 
1618  return caseTempDir.getAbsolutePath();
1619  }
1620 
1627  public String getCacheDirectory() {
1628  return getOrCreateSubdirectory(CACHE_FOLDER);
1629  }
1630 
1637  public String getExportDirectory() {
1638  return getOrCreateSubdirectory(EXPORT_FOLDER);
1639  }
1640 
1647  public String getLogDirectoryPath() {
1648  return getOrCreateSubdirectory(LOG_FOLDER);
1649  }
1650 
1657  public String getReportDirectory() {
1658  return getOrCreateSubdirectory(REPORTS_FOLDER);
1659  }
1660 
1667  public String getConfigDirectory() {
1668  return getOrCreateSubdirectory(CONFIG_FOLDER);
1669  }
1670 
1677  public String getModuleDirectory() {
1678  return getOrCreateSubdirectory(MODULE_FOLDER);
1679  }
1680 
1689  Path path = Paths.get(getModuleDirectory());
1691  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1692  } else {
1693  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1694  }
1695  }
1696 
1706  public List<Content> getDataSources() throws TskCoreException {
1707  return caseDb.getRootObjects();
1708  }
1709 
1715  public Set<TimeZone> getTimeZones() {
1716  Set<TimeZone> timezones = new HashSet<>();
1717  String query = "SELECT time_zone FROM data_source_info";
1718  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1719  ResultSet timeZoneSet = dbQuery.getResultSet();
1720  while (timeZoneSet.next()) {
1721  String timeZone = timeZoneSet.getString("time_zone");
1722  if (timeZone != null && !timeZone.isEmpty()) {
1723  timezones.add(TimeZone.getTimeZone(timeZone));
1724  }
1725  }
1726  } catch (TskCoreException | SQLException ex) {
1727  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1728  }
1729  return timezones;
1730  }
1731 
1738  public String getTextIndexName() {
1739  return getMetadata().getTextIndexName();
1740  }
1741 
1747  public boolean hasData() {
1748  return hasData;
1749  }
1750 
1756  public boolean hasDataSource() {
1757  return hasDataSource;
1758  }
1759 
1770  public void notifyAddingDataSource(UUID eventId) {
1771  hasDataSource = true;
1772  hasData = true;
1773  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1774  }
1775 
1786  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1787  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1788  }
1789 
1801  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1802  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1803  }
1804 
1814  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1815  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1816  }
1817 
1825  public void notifyContentTagAdded(ContentTag newTag) {
1826  notifyContentTagAdded(newTag, null);
1827  }
1828 
1838  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1839  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1840  }
1841 
1849  public void notifyContentTagDeleted(ContentTag deletedTag) {
1850  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1851  }
1852 
1860  public void notifyTagDefinitionChanged(String changedTagName) {
1861  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1862  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1863  }
1864 
1875  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1876  try {
1877  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1878  } catch (NoCurrentCaseException ex) {
1879  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1880  }
1881  }
1882 
1891  notifyBlackBoardArtifactTagAdded(newTag, null);
1892  }
1893 
1903  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1904  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1905  }
1906 
1915  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1916  }
1917 
1929  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1930  addReport(localPath, srcModuleName, reportName, null);
1931  }
1932 
1947  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1948  String normalizedLocalPath;
1949  try {
1950  if (localPath.toLowerCase().contains("http:")) {
1951  normalizedLocalPath = localPath;
1952  } else {
1953  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1954  }
1955  } catch (InvalidPathException ex) {
1956  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1957  throw new TskCoreException(errorMsg, ex);
1958  }
1959  hasData = true;
1960 
1961  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1962  eventPublisher.publish(new ReportAddedEvent(report));
1963  return report;
1964  }
1965 
1974  public List<Report> getAllReports() throws TskCoreException {
1975  return this.caseDb.getAllReports();
1976  }
1977 
1986  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1987  for (Report report : reports) {
1988  this.caseDb.deleteReport(report);
1989  }
1990 
1991  try {
1992  hasData = dbHasData();
1993  } catch (TskCoreException ex) {
1994  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
1995  }
1996 
1997  for (Report report : reports) {
1998  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1999  }
2000  }
2001 
2008  return metadata;
2009  }
2010 
2018  @Messages({
2019  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
2020  })
2021  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
2022  CaseDetails oldCaseDetails = metadata.getCaseDetails();
2023  try {
2024  metadata.setCaseDetails(caseDetails);
2025  } catch (CaseMetadataException ex) {
2026  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
2027  }
2028  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2029  try {
2030  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2031  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
2032  CaseNodeData.writeCaseNodeData(nodeData);
2033  } catch (CaseNodeDataException | InterruptedException ex) {
2034  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2035  }
2036  }
2037  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
2038  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
2039  }
2040  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
2041  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
2042  }
2043  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2044  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
2045  }
2046  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
2047  if (RuntimeProperties.runningWithGUI()) {
2048  SwingUtilities.invokeLater(() -> {
2049  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
2050  try {
2051  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
2052  } catch (Exception ex) {
2053  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
2054  }
2055  });
2056  }
2057  }
2058 
2071  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
2072  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
2073  }
2074 
2080  private Case(CaseMetadata caseMetaData) {
2081  metadata = caseMetaData;
2082  sleuthkitEventListener = new SleuthkitEventListener();
2083  }
2084 
2114  @Messages({
2115  "Case.progressIndicatorCancelButton.label=Cancel",
2116  "Case.progressMessage.preparing=Preparing...",
2117  "Case.progressMessage.cancelling=Cancelling...",
2118  "Case.exceptionMessage.cancelled=Cancelled.",
2119  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
2120  })
2121  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
2122  /*
2123  * Create and start either a GUI progress indicator (with or without a
2124  * cancel button) or a logging progress indicator.
2125  */
2126  CancelButtonListener cancelButtonListener = null;
2127  ProgressIndicator progressIndicator;
2129  if (allowCancellation) {
2130  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
2131  progressIndicator = new ModalDialogProgressIndicator(
2132  mainFrame,
2133  progressIndicatorTitle,
2134  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2135  Bundle.Case_progressIndicatorCancelButton_label(),
2136  cancelButtonListener);
2137  } else {
2138  progressIndicator = new ModalDialogProgressIndicator(
2139  mainFrame,
2140  progressIndicatorTitle);
2141  }
2142  } else {
2143  progressIndicator = new LoggingProgressIndicator();
2144  }
2145  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2146 
2147  /*
2148  * Do the case action in the single thread in the case action executor.
2149  * If the case is a multi-user case, a case lock is acquired and held
2150  * until explictly released and an exclusive case resources lock is
2151  * aquired and held for the duration of the action.
2152  */
2153  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
2154  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
2155  Future<Void> future = caseActionExecutor.submit(() -> {
2156  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2157  caseAction.execute(progressIndicator, additionalParams);
2158  } else {
2159  acquireCaseLock(caseLockType);
2160  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2161  if (null == resourcesLock) {
2162  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2163  }
2164  caseAction.execute(progressIndicator, additionalParams);
2165  } catch (CaseActionException ex) {
2166  releaseCaseLock();
2167  throw ex;
2168  }
2169  }
2170  return null;
2171  });
2172  if (null != cancelButtonListener) {
2173  cancelButtonListener.setCaseActionFuture(future);
2174  }
2175 
2176  /*
2177  * Wait for the case action task to finish.
2178  */
2179  try {
2180  future.get();
2181  } catch (InterruptedException discarded) {
2182  /*
2183  * The thread this method is running in has been interrupted.
2184  */
2185  if (null != cancelButtonListener) {
2186  cancelButtonListener.actionPerformed(null);
2187  } else {
2188  future.cancel(true);
2189  }
2190  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2191  } catch (CancellationException discarded) {
2192  /*
2193  * The case action has been cancelled.
2194  */
2195  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2196  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2197  } catch (ExecutionException ex) {
2198  /*
2199  * The case action has thrown an exception.
2200  */
2201  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2202  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
2203  } finally {
2204  progressIndicator.finish();
2205  }
2206  }
2207 
2223  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2224  assert (additionalParams == null);
2225  try {
2227  createCaseDirectoryIfDoesNotExist(progressIndicator);
2229  switchLoggingToCaseLogsDirectory(progressIndicator);
2231  saveCaseMetadataToFile(progressIndicator);
2233  createCaseNodeData(progressIndicator);
2236  createCaseDatabase(progressIndicator);
2238  openCaseLevelServices(progressIndicator);
2240  openAppServiceCaseResources(progressIndicator, true);
2242  openCommunicationChannels(progressIndicator);
2243  return null;
2244 
2245  } catch (CaseActionException ex) {
2246  /*
2247  * Cancellation or failure. The sleep is a little hack to clear the
2248  * interrupted flag for this thread if this is a cancellation
2249  * scenario, so that the clean up can run to completion in the
2250  * current thread.
2251  */
2252  try {
2253  Thread.sleep(1);
2254  } catch (InterruptedException discarded) {
2255  }
2256  close(progressIndicator);
2257  throw ex;
2258  }
2259  }
2260 
2275  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2276  assert (additionalParams == null);
2277  try {
2279  switchLoggingToCaseLogsDirectory(progressIndicator);
2281  updateCaseNodeData(progressIndicator);
2283  deleteTempfilesFromCaseDirectory(progressIndicator);
2285  openCaseDataBase(progressIndicator);
2287  openCaseLevelServices(progressIndicator);
2289  openAppServiceCaseResources(progressIndicator, false);
2291  openCommunicationChannels(progressIndicator);
2293  checkImagePaths();
2296  return null;
2297 
2298  } catch (CaseActionException ex) {
2299  /*
2300  * Cancellation or failure. The sleep is a little hack to clear the
2301  * interrupted flag for this thread if this is a cancellation
2302  * scenario, so that the clean up can run to completion in the
2303  * current thread.
2304  */
2305  try {
2306  Thread.sleep(1);
2307  } catch (InterruptedException discarded) {
2308  }
2309  close(progressIndicator);
2310  throw ex;
2311  }
2312  }
2313 
2318  @Messages({
2319  "# {0} - paths",
2320  "Case_checkImagePaths_noPaths=The following images had no associated paths: {0}",
2321  "Case_checkImagePaths_exceptionOccurred=An exception occurred while checking if image paths are present"
2322  })
2323  private void checkImagePaths() throws CaseActionException {
2324  // if there is a content provider, images don't necessarily need paths
2325  if (StringUtils.isNotBlank(this.metadata.getContentProviderName())) {
2326  return;
2327  }
2328 
2329  // identify images without paths
2330  try {
2331  List<Image> noPathImages = new ArrayList<>();
2332  List<Image> images = this.caseDb.getImages();
2333  for (Image img: images) {
2334  if (ArrayUtils.isEmpty(img.getPaths())) {
2335  noPathImages.add(img);
2336  }
2337  }
2338 
2339  if (!noPathImages.isEmpty()) {
2340  String imageListStr = noPathImages.stream().map(Image::getName).collect(Collectors.joining(", "));
2341  throw new CaseActionException(Bundle.Case_checkImagePaths_noPaths(imageListStr));
2342  }
2343  } catch (TskCoreException ex) {
2344  throw new CaseActionException(Bundle.Case_checkImagePaths_exceptionOccurred(), ex);
2345  }
2346  }
2347 
2357  @Messages({
2358  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2359  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2360  })
2362  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2363  backgroundOpenFileSystemsFuture.cancel(true);
2364  }
2365 
2367  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2368  }
2369 
2374  private static class BackgroundOpenFileSystemsTask implements Runnable {
2375 
2376  private final SleuthkitCase tskCase;
2377  private final String caseName;
2378  private final long MAX_IMAGE_THRESHOLD = 100;
2380 
2389  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2390  this.tskCase = tskCase;
2391  this.progressIndicator = progressIndicator;
2392  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2393  }
2394 
2402  private void checkIfCancelled() throws InterruptedException {
2403  if (Thread.interrupted()) {
2404  throw new InterruptedException();
2405  }
2406  }
2407 
2413  private List<Image> getImages() {
2414  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2415  try {
2416  return this.tskCase.getImages();
2417  } catch (TskCoreException ex) {
2418  logger.log(
2419  Level.SEVERE,
2420  String.format("Could not obtain images while opening case: %s.", caseName),
2421  ex);
2422 
2423  return null;
2424  }
2425  }
2426 
2436  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2437  byte[] tempBuff = new byte[512];
2438 
2439  for (Image image : images) {
2440  String imageStr = image.getName();
2441 
2442  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2443 
2444  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2445  checkIfCancelled();
2446  for (FileSystem fileSystem : fileSystems) {
2447  fileSystem.read(tempBuff, 0, 512);
2448  checkIfCancelled();
2449  }
2450 
2451  }
2452  }
2453 
2454  @Override
2455  public void run() {
2456  try {
2457  checkIfCancelled();
2458  List<Image> images = getImages();
2459  if (images == null) {
2460  return;
2461  }
2462 
2463  if (images.size() > MAX_IMAGE_THRESHOLD) {
2464  // If we have a large number of images, don't try to preload anything
2465  logger.log(
2466  Level.INFO,
2467  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2468  return;
2469  }
2470 
2471  checkIfCancelled();
2472  openFileSystems(images);
2473  } catch (InterruptedException ex) {
2474  logger.log(
2475  Level.INFO,
2476  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2477  } catch (Exception ex) {
2478  // Exception firewall
2479  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2480  }
2481  }
2482 
2483  }
2484 
2499  @Messages({
2500  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2501  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2502  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2503  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2504  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2505  assert (additionalParams instanceof Long);
2506  open(progressIndicator, null);
2507  try {
2508  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2509  Long dataSourceObjectID = (Long) additionalParams;
2510  try {
2511  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2512  if (dataSource == null) {
2513  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2514  }
2515  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2516  } catch (TskDataException | TskCoreException ex) {
2517  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2518  }
2519  try {
2520  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2521  } catch (KeywordSearchServiceException ex) {
2522  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2523  }
2524  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2525  return null;
2526  } finally {
2527  close(progressIndicator);
2528  releaseCaseLock();
2529  }
2530  }
2531 
2542  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2543 
2544  if (portableCaseFolder.exists()) {
2545  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2546  }
2547  if (!portableCaseFolder.mkdirs()) {
2548  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2549  }
2550 
2551  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2553  try {
2554  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2555  caseName, details, metadata);
2556  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2557  } catch (CaseMetadataException ex) {
2558  throw new TskCoreException("Error creating case metadata", ex);
2559  }
2560 
2561  // Create the Sleuthkit case
2562  SleuthkitCase portableSleuthkitCase;
2563  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2564  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2565 
2566  return portableSleuthkitCase;
2567  }
2568 
2579  if (Thread.currentThread().isInterrupted()) {
2580  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2581  }
2582  }
2583 
2594  @Messages({
2595  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2596  })
2597  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2598  /*
2599  * TODO (JIRA-2180): Always create the case directory as part of the
2600  * case creation process.
2601  */
2602  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2603  if (new File(metadata.getCaseDirectory()).exists() == false) {
2604  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2605  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2606  }
2607  }
2608 
2615  @Messages({
2616  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2617  })
2618  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2619  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2621  }
2622 
2634  @Messages({
2635  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2636  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2637  })
2638  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2639  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2640  try {
2641  this.metadata.writeToFile();
2642  } catch (CaseMetadataException ex) {
2643  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2644  }
2645  }
2646 
2658  @Messages({
2659  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2660  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2661  })
2662  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2664  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2665  try {
2666  CaseNodeData.createCaseNodeData(metadata);
2667  } catch (CaseNodeDataException | InterruptedException ex) {
2668  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2669  }
2670  }
2671  }
2672 
2684  @Messages({
2685  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2686  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2687  })
2688  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2690  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2691  try {
2693  nodeData.setLastAccessDate(new Date());
2694  CaseNodeData.writeCaseNodeData(nodeData);
2695  } catch (CaseNodeDataException | InterruptedException ex) {
2696  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2697  }
2698  }
2699  }
2700 
2706  @Messages({
2707  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2708  })
2709  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2710  /*
2711  * Clear the temp subdirectory of the case directory.
2712  */
2713  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2714  FileUtil.deleteDir(new File(this.getTempDirectory()));
2715  }
2716 
2728  @Messages({
2729  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2730  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2731  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2732  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2733  })
2734  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2735  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2736  try {
2737  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2738  /*
2739  * For single-user cases, the case database is a SQLite database
2740  * with a standard name, physically located in the case
2741  * directory.
2742  */
2743  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString(), (ContentStreamProvider) null, APP_NAME);
2744  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2745  } else {
2746  /*
2747  * For multi-user cases, the case database is a PostgreSQL
2748  * database with a name derived from the case display name,
2749  * physically located on the PostgreSQL database server.
2750  */
2752  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2753  }
2754  } catch (TskCoreException ex) {
2756  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2757  } catch (UserPreferencesException ex) {
2758  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2759  } catch (CaseMetadataException ex) {
2760  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2761  }
2762  }
2763 
2775  @Messages({
2776  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2777  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2778  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2779  "Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.",
2780  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2781  })
2782  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2783  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2784  try {
2785  String databaseName = metadata.getCaseDatabaseName();
2786 
2788  if (StringUtils.isNotBlank(metadata.getContentProviderName()) && contentProvider == null) {
2789  if (metadata.getContentProviderName().trim().toUpperCase().startsWith(CT_PROVIDER_PREFIX.toUpperCase())) {
2790  new CTIntegrationMissingDialog(WindowManager.getDefault().getMainWindow(), true).showDialog(null);
2791  }
2792  throw new CaseActionException(Bundle.Case_exceptionMessage_contentProviderCouldNotBeFound());
2793  }
2794 
2795  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2796  caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath(), contentProvider, APP_NAME);
2798  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory(), contentProvider);
2799  } else {
2800  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2801  }
2804  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2805  } catch (UserPreferencesException ex) {
2806  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2807  } catch (TskCoreException ex) {
2809  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2810  }
2811  }
2812 
2813 
2819  @Messages({
2820  "# {0} - appplicationName",
2821  "Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy.",
2822  "Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException_defaultApp=another application"
2823  })
2824  private void throwIfConcurrentDbAccessException(Exception ex) throws CaseActionException {
2825  ConcurrentDbAccessException concurrentEx = null;
2826  Throwable curEx = ex;
2827  // max depth search for a concurrent db access exception will be 10
2828  for (int i = 0; i < 10; i++) {
2829  if (curEx == null) {
2830  break;
2831  } else if (curEx instanceof ConcurrentDbAccessException foundEx) {
2832  concurrentEx = foundEx;
2833  break;
2834  } else {
2835  curEx = curEx.getCause();
2836  }
2837  }
2838 
2839  if (concurrentEx != null) {
2840  throw new CaseActionException(Bundle.Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException(
2841  StringUtils.defaultIfBlank(concurrentEx.getConflictingApplicationName(),
2842  Bundle.Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException_defaultApp())
2843  ), concurrentEx);
2844  }
2845  }
2846 
2847 
2857  private static ContentStreamProvider loadContentProvider(String providerName) {
2858  Collection<? extends AutopsyContentProvider> customContentProviders = Lookup.getDefault().lookupAll(AutopsyContentProvider.class);
2859  if (customContentProviders != null) {
2860  for (AutopsyContentProvider customProvider : customContentProviders) {
2861  // ensure the provider matches the name
2862  if (customProvider == null || !StringUtils.equalsIgnoreCase(providerName, customProvider.getName())) {
2863  continue;
2864  }
2865 
2866  ContentStreamProvider contentProvider = customProvider.load();
2867  if (contentProvider != null) {
2868  return contentProvider;
2869  }
2870  }
2871  }
2872 
2873  return null;
2874  }
2875 
2876 
2883  @Messages({
2884  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2885  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2886  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2887  this.caseServices = new Services(caseDb);
2888  /*
2889  * RC Note: JM put this initialization here. I'm not sure why. However,
2890  * my attempt to put it in the openCaseDatabase method seems to lead to
2891  * intermittent unchecked exceptions concerning a missing subscriber.
2892  */
2893  caseDb.registerForEvents(sleuthkitEventListener);
2894  }
2895 
2908  @NbBundle.Messages({
2909  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2910  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2911  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2912  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2913  })
2914  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2915  /*
2916  * Each service gets its own independently cancellable/interruptible
2917  * task, running in a named thread managed by an executor service, with
2918  * its own progress indicator. This allows for cancellation of the
2919  * opening of case resources for individual services. It also makes it
2920  * possible to ensure that each service task completes before the next
2921  * one starts by awaiting termination of the executor service.
2922  */
2923  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2924 
2925  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2926  )) {
2927  /*
2928  * Create a progress indicator for the task and start the task. If
2929  * running with a GUI, the progress indicator will be a dialog box
2930  * with a Cancel button.
2931  */
2932  CancelButtonListener cancelButtonListener = null;
2933  ProgressIndicator appServiceProgressIndicator;
2935  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2936  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2937  mainFrame,
2938  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2939  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2940  Bundle.Case_progressIndicatorCancelButton_label(),
2941  cancelButtonListener);
2942  } else {
2943  appServiceProgressIndicator = new LoggingProgressIndicator();
2944  }
2945  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2946  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2947  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2948  threadNameSuffix = threadNameSuffix.toLowerCase();
2949  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2950  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2951  Future<Void> future = executor.submit(() -> {
2952  service.openCaseResources(context);
2953  return null;
2954  });
2955  if (null != cancelButtonListener) {
2956  cancelButtonListener.setCaseContext(context);
2957  cancelButtonListener.setCaseActionFuture(future);
2958  }
2959 
2960  /*
2961  * Wait for the task to either be completed or
2962  * cancelled/interrupted, or for the opening of the case to be
2963  * cancelled.
2964  */
2965  try {
2966  future.get();
2967  } catch (InterruptedException discarded) {
2968  /*
2969  * The parent create/open case task has been cancelled.
2970  */
2971  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()));
2972  future.cancel(true);
2973  } catch (CancellationException discarded) {
2974  /*
2975  * The opening of case resources by the application service has
2976  * been cancelled, so the executor service has thrown. Note that
2977  * there is no guarantee the task itself has responded to the
2978  * cancellation request yet.
2979  */
2980  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()));
2981  } catch (ExecutionException ex) {
2982  /*
2983  * An exception was thrown while executing the task. The
2984  * case-specific application service resources are not
2985  * essential. Log an error and notify the user if running the
2986  * desktop GUI, but do not throw.
2987  */
2988  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2990  SwingUtilities.invokeLater(() -> {
2991  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2992  });
2993  }
2994  } finally {
2995  /*
2996  * Shut down the executor service and wait for it to finish.
2997  * This ensures that the task has finished. Without this, it
2998  * would be possible to start the next task before the current
2999  * task responded to a cancellation request.
3000  */
3002  appServiceProgressIndicator.finish();
3003  }
3005  }
3006  }
3007 
3019  @Messages({
3020  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
3021  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
3022  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
3023  })
3024  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
3025  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
3026  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
3027  try {
3028  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
3030  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
3031  } catch (AutopsyEventException ex) {
3032  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
3033  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
3034  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
3035  }
3036  }
3037  }
3038 
3053  private void doCloseCaseAction() throws CaseActionException {
3054  /*
3055  * Set up either a GUI progress indicator without a Cancel button or a
3056  * logging progress indicator.
3057  */
3058  ProgressIndicator progressIndicator;
3060  progressIndicator = new ModalDialogProgressIndicator(
3061  mainFrame,
3062  Bundle.Case_progressIndicatorTitle_closingCase());
3063  } else {
3064  progressIndicator = new LoggingProgressIndicator();
3065  }
3066  progressIndicator.start(Bundle.Case_progressMessage_preparing());
3067 
3068  /*
3069  * Closing a case is always done in the same non-UI thread that
3070  * opened/created the case. If the case is a multi-user case, this
3071  * ensures that case lock that is held as long as the case is open is
3072  * released in the same thread in which it was acquired, as is required
3073  * by the coordination service.
3074  */
3075  Future<Void> future = caseActionExecutor.submit(() -> {
3076  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
3077  close(progressIndicator);
3078  } else {
3079  /*
3080  * Acquire an exclusive case resources lock to ensure only one
3081  * node at a time can create/open/upgrade/close the case
3082  * resources.
3083  */
3084  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
3085  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
3086  if (null == resourcesLock) {
3087  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
3088  }
3089  close(progressIndicator);
3090  } finally {
3091  /*
3092  * Always release the case directory lock that was acquired
3093  * when the case was opened.
3094  */
3095  releaseCaseLock();
3096  }
3097  }
3098  return null;
3099  });
3100 
3101  try {
3102  future.get();
3103  } catch (InterruptedException | CancellationException unused) {
3104  /*
3105  * The wait has been interrupted by interrupting the thread running
3106  * this method. Not allowing cancellation of case closing, so ignore
3107  * the interrupt. Likewise, cancellation of the case closing task is
3108  * not supported.
3109  */
3110  } catch (ExecutionException ex) {
3111  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
3112  } finally {
3113  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
3114  progressIndicator.finish();
3115  }
3116  }
3117 
3123  @Messages({
3124  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
3125  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
3126  "Case.progressMessage.closingCaseDatabase=Closing case database..."
3127  })
3128  private void close(ProgressIndicator progressIndicator) {
3130 
3131  /*
3132  * Stop sending/receiving case events to and from other nodes if this is
3133  * a multi-user case.
3134  */
3135  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
3136  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
3137  if (null != collaborationMonitor) {
3138  collaborationMonitor.shutdown();
3139  }
3140  eventPublisher.closeRemoteEventChannel();
3141  }
3142 
3143  /*
3144  * Allow all registered application services providers to close
3145  * resources related to the case.
3146  */
3147  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
3149 
3150  /*
3151  * Close the case database.
3152  */
3153  if (null != caseDb) {
3154  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
3155  caseDb.unregisterForEvents(sleuthkitEventListener);
3156  caseDb.close();
3157  }
3158 
3162  deleteTempfilesFromCaseDirectory(progressIndicator);
3163 
3164  /*
3165  * Switch the log directory.
3166  */
3167  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
3169  }
3170 
3175  @Messages({
3176  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
3177  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
3178  })
3180  /*
3181  * Each service gets its own independently cancellable task, and thus
3182  * its own task progress indicator.
3183  */
3184  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
3185  )) {
3186  ProgressIndicator progressIndicator;
3188  progressIndicator = new ModalDialogProgressIndicator(
3189  mainFrame,
3190  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
3191  } else {
3192  progressIndicator = new LoggingProgressIndicator();
3193  }
3194  progressIndicator.start(Bundle.Case_progressMessage_preparing());
3195  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
3196  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
3197  threadNameSuffix = threadNameSuffix.toLowerCase();
3198  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
3199  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
3200  Future<Void> future = executor.submit(() -> {
3201  service.closeCaseResources(context);
3202  return null;
3203  });
3204  try {
3205  future.get();
3206  } catch (InterruptedException ex) {
3207  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
3208  } catch (CancellationException ex) {
3209  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
3210  } catch (ExecutionException ex) {
3211  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
3213  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
3214  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
3215  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
3216  }
3217  } finally {
3219  progressIndicator.finish();
3220  }
3221  }
3222  }
3223 
3229  @Messages({
3230  "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.",
3231  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case."
3232  })
3233  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
3234  String caseDir = metadata.getCaseDirectory();
3235  try {
3236  CoordinationService coordinationService = CoordinationService.getInstance();
3237  caseLock = lockType == CaseLockType.SHARED
3238  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
3239  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
3240  if (caseLock == null) {
3241  if (lockType == CaseLockType.SHARED) {
3242  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
3243  } else {
3244  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
3245  }
3246  }
3247  } catch (InterruptedException | CoordinationServiceException ex) {
3248  if (lockType == CaseLockType.SHARED) {
3249  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
3250  } else {
3251  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
3252  }
3253  }
3254  }
3255 
3259  private void releaseCaseLock() {
3260  if (caseLock != null) {
3261  try {
3262  caseLock.release();
3263  caseLock = null;
3265  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
3266  }
3267  }
3268  }
3269 
3276  private String getOrCreateSubdirectory(String subDirectoryName) {
3277  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
3278  if (!subDirectory.exists()) {
3279  subDirectory.mkdirs();
3280  }
3281  return subDirectory.toString();
3282 
3283  }
3284 
3296  @Messages({
3297  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
3298  })
3299  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3300  boolean errorsOccurred = false;
3301  try {
3302  deleteTextIndex(metadata, progressIndicator);
3303  } catch (KeywordSearchServiceException ex) {
3304  errorsOccurred = true;
3305  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
3306  }
3307 
3308  try {
3309  deleteCaseDirectory(metadata, progressIndicator);
3310  } catch (CaseActionException ex) {
3311  errorsOccurred = true;
3312  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
3313  }
3314 
3315  deleteFromRecentCases(metadata, progressIndicator);
3316 
3317  if (errorsOccurred) {
3318  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3319  }
3320  }
3321 
3341  @Messages({
3342  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
3343  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
3344  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
3345  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
3346  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
3347  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
3348  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
3349  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
3350  })
3351  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
3352  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
3353  CoordinationService coordinationService;
3354  try {
3355  coordinationService = CoordinationService.getInstance();
3356  } catch (CoordinationServiceException ex) {
3357  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
3358  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
3359  }
3360 
3361  CaseNodeData caseNodeData;
3362  boolean errorsOccurred = false;
3363  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
3364  if (dirLock == null) {
3365  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
3366  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
3367  }
3368 
3369  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
3370  try {
3371  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
3372  } catch (CaseNodeDataException | InterruptedException ex) {
3373  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
3374  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
3375  }
3376 
3377  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
3378 
3379  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
3380  try {
3381  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
3382  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
3383  } catch (CoordinationServiceException ex) {
3384  if (!isNoNodeException(ex)) {
3385  errorsOccurred = true;
3386  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
3387  }
3388  } catch (InterruptedException ex) {
3389  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
3390  }
3391 
3392  } catch (CoordinationServiceException ex) {
3393  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
3394  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
3395  }
3396 
3397  if (!errorsOccurred) {
3398  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3399  try {
3400  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3401  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3402  } catch (CoordinationServiceException | InterruptedException ex) {
3403  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
3404  errorsOccurred = true;
3405  }
3406  }
3407 
3408  if (errorsOccurred) {
3409  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3410  }
3411  }
3412 
3437  @Beta
3438  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3439  boolean errorsOccurred = false;
3440  try {
3441  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3442  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3443  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3444  deleteFromRecentCases(metadata, progressIndicator);
3445  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3446  errorsOccurred = true;
3447  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
3448  } catch (KeywordSearchServiceException ex) {
3449  errorsOccurred = true;
3450  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
3451  } catch (CaseActionException ex) {
3452  errorsOccurred = true;
3453  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
3454  }
3455  return errorsOccurred;
3456  }
3457 
3478  @Messages({
3479  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3480  })
3481  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3482  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3483  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3484  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3486  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3487  Class.forName("org.postgresql.Driver"); //NON-NLS
3488  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3489  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3490  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3491  if (queryResult.next()) {
3492  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3493  statement.execute(deleteCommand);
3494  }
3495  }
3496  }
3498  }
3499  }
3500 
3516  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3518  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3519  deleteTextIndex(metadata, progressIndicator);
3521  }
3522  }
3523 
3533  @Messages({
3534  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3535  })
3536  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3537  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3538 
3539  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3540  )) {
3541  searchService.deleteTextIndex(metadata);
3542  }
3543  }
3544 
3559  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3560  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3561  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3562  deleteCaseDirectory(metadata, progressIndicator);
3564  }
3565  }
3566 
3576  @Messages({
3577  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3578  })
3579  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3580  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3581  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3582  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3583  }
3584  }
3585 
3593  @Messages({
3594  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3595  })
3596  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3598  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3599  SwingUtilities.invokeLater(() -> {
3600  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3601  });
3602  }
3603  }
3604 
3615  boolean isNodeNodeEx = false;
3616  Throwable cause = ex.getCause();
3617  if (cause != null) {
3618  String causeMessage = cause.getMessage();
3619  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3620  }
3621  return isNodeNodeEx;
3622  }
3623 
3635  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3636  try {
3637  caseNodeData.setDeletedFlag(flag);
3638  CaseNodeData.writeCaseNodeData(caseNodeData);
3639  } catch (CaseNodeDataException ex) {
3640  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);
3641 
3642  }
3643  }
3644 
3656  private void updateDataParameters() throws TskCoreException {
3657  hasDataSource = dbHasDataSource();
3658 
3659  if (!hasDataSource) {
3660  hasData = dbHasData();
3661  } else {
3662  hasData = true;
3663  }
3664  }
3665 
3673  private boolean dbHasDataSource() throws TskCoreException {
3674  String query = "SELECT count(*) AS count FROM (SELECT * FROM data_source_info LIMIT 1)t";
3675  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3676  ResultSet resultSet = dbQuery.getResultSet();
3677  if (resultSet.next()) {
3678  return resultSet.getLong("count") > 0;
3679  }
3680  return false;
3681  } catch (SQLException ex) {
3682  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3683  throw new TskCoreException("Error accessing case databse", ex);
3684  }
3685  }
3686 
3695  private boolean dbHasData() throws TskCoreException {
3696  // The LIMIT 1 in the subquery should limit the data returned and
3697  // make the overall query more efficent.
3698  String query = "SELECT SUM(cnt) total FROM "
3699  + "(SELECT COUNT(*) AS cnt FROM "
3700  + "(SELECT * FROM tsk_objects LIMIT 1)t "
3701  + "UNION ALL "
3702  + "SELECT COUNT(*) AS cnt FROM "
3703  + "(SELECT * FROM tsk_hosts LIMIT 1)r) s";
3704  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3705  ResultSet resultSet = dbQuery.getResultSet();
3706  if (resultSet.next()) {
3707  return resultSet.getLong("total") > 0;
3708  } else {
3709  return false;
3710  }
3711  } catch (SQLException ex) {
3712  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3713  throw new TskCoreException("Error accessing case databse", ex);
3714  }
3715  }
3716 
3725  private interface CaseAction<T, V, R> {
3726 
3737  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3738  }
3739 
3744  private enum CaseLockType {
3745  SHARED, EXCLUSIVE;
3746  }
3747 
3752  @ThreadSafe
3753  private final static class CancelButtonListener implements ActionListener {
3754 
3755  private final String cancellationMessage;
3756  @GuardedBy("this")
3757  private boolean cancelRequested;
3758  @GuardedBy("this")
3760  @GuardedBy("this")
3761  private Future<?> caseActionFuture;
3762 
3771  private CancelButtonListener(String cancellationMessage) {
3772  this.cancellationMessage = cancellationMessage;
3773  }
3774 
3780  private synchronized void setCaseContext(CaseContext caseContext) {
3781  this.caseContext = caseContext;
3782  /*
3783  * If the cancel button has already been pressed, pass the
3784  * cancellation on to the case context.
3785  */
3786  if (cancelRequested) {
3787  cancel();
3788  }
3789  }
3790 
3796  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3797  this.caseActionFuture = caseActionFuture;
3798  /*
3799  * If the cancel button has already been pressed, cancel the Future
3800  * of the task.
3801  */
3802  if (cancelRequested) {
3803  cancel();
3804  }
3805  }
3806 
3812  @Override
3813  public synchronized void actionPerformed(ActionEvent event) {
3814  cancel();
3815  }
3816 
3820  private void cancel() {
3821  /*
3822  * At a minimum, set the cancellation requested flag of this
3823  * listener.
3824  */
3825  this.cancelRequested = true;
3826  if (null != this.caseContext) {
3827  /*
3828  * Set the cancellation request flag and display the
3829  * cancellation message in the progress indicator for the case
3830  * context associated with this listener.
3831  */
3833  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3834  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3835  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3836  }
3837  }
3838  this.caseContext.requestCancel();
3839  }
3840  if (null != this.caseActionFuture) {
3841  /*
3842  * Cancel the Future of the task associated with this listener.
3843  * Note that the task thread will be interrupted if the task is
3844  * blocked.
3845  */
3846  this.caseActionFuture.cancel(true);
3847  }
3848  }
3849  }
3850 
3854  private static class TaskThreadFactory implements ThreadFactory {
3855 
3856  private final String threadName;
3857 
3858  private TaskThreadFactory(String threadName) {
3859  this.threadName = threadName;
3860  }
3861 
3862  @Override
3863  public Thread newThread(Runnable task) {
3864  return new Thread(task, threadName);
3865  }
3866 
3867  }
3868 
3876  @Deprecated
3877  public static String getAppName() {
3878  return UserPreferences.getAppName();
3879  }
3880 
3900  @Deprecated
3901  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3902  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3903  }
3904 
3925  @Deprecated
3926  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3927  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3928  }
3929 
3941  @Deprecated
3942  public static void open(String caseMetadataFilePath) throws CaseActionException {
3943  openAsCurrentCase(caseMetadataFilePath);
3944  }
3945 
3955  @Deprecated
3956  public void closeCase() throws CaseActionException {
3957  closeCurrentCase();
3958  }
3959 
3965  @Deprecated
3966  public static void invokeStartupDialog() {
3968  }
3969 
3983  @Deprecated
3984  public static String convertTimeZone(String timeZoneId) {
3985  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3986  }
3987 
3997  @Deprecated
3998  public static boolean pathExists(String filePath) {
3999  return new File(filePath).isFile();
4000  }
4001 
4010  @Deprecated
4011  public static String getAutopsyVersion() {
4012  return Version.getVersion();
4013  }
4014 
4022  @Deprecated
4023  public static boolean existsCurrentCase() {
4024  return isCaseOpen();
4025  }
4026 
4036  @Deprecated
4037  public static String getModulesOutputDirRelPath() {
4038  return "ModuleOutput"; //NON-NLS
4039  }
4040 
4050  @Deprecated
4051  public static PropertyChangeSupport
4053  return new PropertyChangeSupport(Case.class
4054  );
4055  }
4056 
4065  @Deprecated
4066  public String getModulesOutputDirAbsPath() {
4067  return getModuleDirectory();
4068  }
4069 
4084  @Deprecated
4085  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
4086  try {
4087  Image newDataSource = caseDb.getImageById(imgId);
4088  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
4089  return newDataSource;
4090  } catch (TskCoreException ex) {
4091  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
4092  }
4093  }
4094 
4102  @Deprecated
4103  public Set<TimeZone> getTimeZone() {
4104  return getTimeZones();
4105  }
4106 
4117  @Deprecated
4118  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
4119  deleteReports(reports);
4120  }
4121 
4122 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:187
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1849
Case(CaseMetadata caseMetaData)
Definition: Case.java:2080
static CaseType fromString(String typeName)
Definition: Case.java:235
final SleuthkitEventListener sleuthkitEventListener
Definition: Case.java:198
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:182
static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag)
Definition: Case.java:3635
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:829
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1914
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2275
CoordinationService.Lock caseLock
Definition: Case.java:196
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3438
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:4085
void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event)
Definition: Case.java:541
static synchronized IngestManager getInstance()
static final ExecutorService openFileSystemsExecutor
Definition: Case.java:191
static final String NO_NODE_ERROR_MSG_FRAGMENT
Definition: Case.java:184
void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event)
Definition: Case.java:618
void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event)
Definition: Case.java:590
void acquireCaseLock(CaseLockType lockType)
Definition: Case.java:3233
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:687
void start(String message, int totalWorkUnits)
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List< BlackboardArtifactTag > removedTagList)
Definition: Case.java:1903
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:189
static final String EXPORT_FOLDER
Definition: Case.java:177
void throwIfConcurrentDbAccessException(Exception ex)
Definition: Case.java:2824
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:1171
void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event)
Definition: Case.java:556
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1860
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1838
static volatile Frame mainFrame
Definition: Case.java:192
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3984
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3299
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event)
Definition: Case.java:634
void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event)
Definition: Case.java:530
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
volatile ExecutorService caseActionExecutor
Definition: Case.java:195
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1875
static final String APP_NAME
Definition: Case.java:172
static final String CACHE_FOLDER
Definition: Case.java:176
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3559
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:2071
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1929
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1289
Map< Long, List< String > > getImagePaths()
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1814
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3926
void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event)
Definition: Case.java:629
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:170
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:174
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:1033
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:4118
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3780
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:171
static boolean isValidName(String caseName)
Definition: Case.java:771
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2782
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2597
CollaborationMonitor collaborationMonitor
Definition: Case.java:199
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:3024
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:4037
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2638
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3516
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:2121
void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event)
Definition: Case.java:639
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3813
static final String MODULE_FOLDER
Definition: Case.java:181
static final String CT_PROVIDER_PREFIX
Definition: Case.java:185
void unregisterForEvents(Object listener)
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2885
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3901
synchronized void openRemoteEventChannel(String channelName)
void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event)
Definition: Case.java:607
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2662
void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event)
Definition: Case.java:644
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:1131
static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3596
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:856
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:747
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static boolean isNoNodeException(CoordinationServiceException ex)
Definition: Case.java:3614
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:3128
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1890
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:4052
void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event)
Definition: Case.java:546
Report addReport(String localPath, String sourceModuleName, String reportName)
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:675
void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event)
Definition: Case.java:579
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3796
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2734
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:737
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2618
void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event)
Definition: Case.java:664
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3536
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2709
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1986
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1801
static ContentStreamProvider loadContentProvider(String providerName)
Definition: Case.java:2857
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1947
static boolean pathExists(String filePath)
Definition: Case.java:3998
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2542
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3942
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:1082
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1263
Collection< FileSystem > getImageFileSystems(Image image)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:3276
boolean equalsName(String otherTypeName)
Definition: Case.java:293
static final String EVENT_CHANNEL_NAME
Definition: Case.java:175
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event)
Definition: Case.java:535
static SleuthkitCase openCase(String dbPath)
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:712
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:178
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2223
static SleuthkitCase newCase(String dbPath)
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:193
void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase)
Definition: Case.java:2914
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1770
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:727
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1825
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
DataSource getDataSource(long objectId)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:183
void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event)
Definition: Case.java:567
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:802
void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event)
Definition: Case.java:649
static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3579
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1786
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3351
static final String CONFIG_FOLDER
Definition: Case.java:180
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:757
static final Object caseActionSerializationLock
Definition: Case.java:188
static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3481
void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event)
Definition: Case.java:654
static final String REPORTS_FOLDER
Definition: Case.java:179
static final String TEMP_FOLDER
Definition: Case.java:173
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:702
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2688
CaseDbQuery executeQuery(String query)
void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event)
Definition: Case.java:659

Copyright © 2012-2024 Sleuth Kit Labs. Generated on: Mon Feb 17 2025
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.