Autopsy  4.8.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 2011-2018 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 
21 import java.awt.Frame;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.beans.PropertyChangeListener;
25 import java.beans.PropertyChangeSupport;
26 import java.io.File;
27 import java.io.IOException;
28 import java.nio.file.InvalidPathException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.sql.Connection;
32 import java.sql.DriverManager;
33 import java.sql.SQLException;
34 import java.sql.Statement;
35 import java.text.SimpleDateFormat;
36 import java.util.Collection;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.MissingResourceException;
43 import java.util.Set;
44 import java.util.TimeZone;
45 import java.util.UUID;
46 import java.util.concurrent.CancellationException;
47 import java.util.concurrent.ExecutionException;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors;
50 import java.util.concurrent.Future;
51 import java.util.concurrent.ThreadFactory;
52 import java.util.concurrent.TimeUnit;
53 import java.util.logging.Level;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56 import javax.annotation.concurrent.GuardedBy;
57 import javax.annotation.concurrent.ThreadSafe;
58 import javax.swing.JOptionPane;
59 import javax.swing.SwingUtilities;
60 import org.openide.util.Lookup;
61 import org.openide.util.NbBundle;
62 import org.openide.util.NbBundle.Messages;
63 import org.openide.util.actions.CallableSystemAction;
64 import org.openide.windows.WindowManager;
68 import static org.sleuthkit.autopsy.casemodule.Bundle.*;
110 import org.sleuthkit.datamodel.BlackboardArtifactTag;
111 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
112 import org.sleuthkit.datamodel.Content;
113 import org.sleuthkit.datamodel.ContentTag;
114 import org.sleuthkit.datamodel.Image;
115 import org.sleuthkit.datamodel.Report;
116 import org.sleuthkit.datamodel.SleuthkitCase;
117 import org.sleuthkit.datamodel.TskCoreException;
118 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
119 
123 public class Case {
124 
125  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
126  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
127  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
128  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
129  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
130  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
131  private static final String LOG_FOLDER = "Log"; //NON-NLS
132  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
133  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
134  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
135  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
136  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
137  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
138  private static final Logger logger = Logger.getLogger(Case.class.getName());
140  private static final Object caseActionSerializationLock = new Object();
141  private static volatile Frame mainFrame;
142  private static volatile Case currentCase;
143  private final CaseMetadata metadata;
144  private volatile ExecutorService caseLockingExecutor;
146  private SleuthkitCase caseDb;
147  private CollaborationMonitor collaborationMonitor;
149  private boolean hasDataSources;
150 
151  /*
152  * Get a reference to the main window of the desktop application to use to
153  * parent pop up dialogs and initialize the application name for use in
154  * changing the main window title.
155  */
156  static {
157  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
158  @Override
159  public void run() {
160  mainFrame = WindowManager.getDefault().getMainWindow();
161  }
162  });
163  }
164 
168  public enum CaseType {
169 
170  SINGLE_USER_CASE("Single-user case"), //NON-NLS
171  MULTI_USER_CASE("Multi-user case"); //NON-NLS
172 
173  private final String typeName;
174 
182  public static CaseType fromString(String typeName) {
183  if (typeName != null) {
184  for (CaseType c : CaseType.values()) {
185  if (typeName.equalsIgnoreCase(c.toString())) {
186  return c;
187  }
188  }
189  }
190  return null;
191  }
192 
198  @Override
199  public String toString() {
200  return typeName;
201  }
202 
208  @Messages({
209  "Case_caseType_singleUser=Single-user case",
210  "Case_caseType_multiUser=Multi-user case"
211  })
213  if (fromString(typeName) == SINGLE_USER_CASE) {
214  return Bundle.Case_caseType_singleUser();
215  } else {
216  return Bundle.Case_caseType_multiUser();
217  }
218  }
219 
225  private CaseType(String typeName) {
226  this.typeName = typeName;
227  }
228 
239  @Deprecated
240  public boolean equalsName(String otherTypeName) {
241  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
242  }
243 
244  };
245 
250  public enum Events {
251 
259  @Deprecated
268  @Deprecated
277  @Deprecated
382 
383  };
384 
391  public static void addPropertyChangeListener(PropertyChangeListener listener) {
392  addEventSubscriber(Stream.of(Events.values())
393  .map(Events::toString)
394  .collect(Collectors.toSet()), listener);
395  }
396 
403  public static void removePropertyChangeListener(PropertyChangeListener listener) {
404  removeEventSubscriber(Stream.of(Events.values())
405  .map(Events::toString)
406  .collect(Collectors.toSet()), listener);
407  }
408 
417  @Deprecated
418  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
419  eventPublisher.addSubscriber(eventNames, subscriber);
420  }
421 
428  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
429  eventTypes.forEach((Events event) -> {
430  eventPublisher.addSubscriber(event.toString(), subscriber);
431  });
432  }
433 
442  @Deprecated
443  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
444  eventPublisher.addSubscriber(eventName, subscriber);
445  }
446 
453  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
454  eventPublisher.removeSubscriber(eventName, subscriber);
455  }
456 
463  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
464  eventPublisher.removeSubscriber(eventNames, subscriber);
465  }
466 
473  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
474  eventTypes.forEach((Events event) -> {
475  eventPublisher.removeSubscriber(event.toString(), subscriber);
476  });
477  }
478 
487  public static boolean isValidName(String caseName) {
488  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
489  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
490  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
491  }
492 
517  @Deprecated
518  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
519  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
520  }
521 
541  @Messages({
542  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
543  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
544  })
545  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
546  if (caseDetails.getCaseDisplayName().isEmpty()) {
547  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
548  }
549  if (caseDir.isEmpty()) {
550  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
551  }
552  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
553  }
554 
568  @Messages({
569  "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
570  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
571  })
572  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
574  try {
575  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
576  } catch (CaseMetadataException ex) {
577  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex);
578  }
580  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
581  }
582  openAsCurrentCase(new Case(metadata), false);
583  }
584 
590  public static boolean isCaseOpen() {
591  return currentCase != null;
592  }
593 
601  public static Case getCurrentCase() {
602  try {
603  return getCurrentCaseThrows();
604  } catch (NoCurrentCaseException ex) {
605  /*
606  * Throw a runtime exception, since this is a programming error.
607  */
608  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
609  }
610  }
611 
630  Case openCase = currentCase;
631  if (openCase == null) {
632  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
633  } else {
634  return openCase;
635  }
636  }
637 
646  @Messages({
647  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
648  "Case.progressIndicatorTitle.closingCase=Closing Case"
649  })
650  public static void closeCurrentCase() throws CaseActionException {
651  synchronized (caseActionSerializationLock) {
652  if (null == currentCase) {
653  return;
654  }
655  Case closedCase = currentCase;
656  try {
657  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
658  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
659  currentCase = null;
660  closedCase.close();
661  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
662  } catch (CaseActionException ex) {
663  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
664  throw ex;
665  } finally {
668  }
669  }
670  }
671  }
672 
681  public static void deleteCurrentCase() throws CaseActionException {
682  synchronized (caseActionSerializationLock) {
683  if (null == currentCase) {
684  return;
685  }
686  CaseMetadata metadata = currentCase.getMetadata();
688  deleteCase(metadata);
689  }
690  }
691 
703  @Messages({
704  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
705  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
706  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
707  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
708  })
709  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
710  synchronized (caseActionSerializationLock) {
711  if (null != currentCase) {
712  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
713  }
714  }
715 
716  /*
717  * Set up either a GUI progress indicator without a cancel button (can't
718  * cancel deleting a case) or a logging progress indicator.
719  */
720  ProgressIndicator progressIndicator;
722  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
723  } else {
724  progressIndicator = new LoggingProgressIndicator();
725  }
726  progressIndicator.start(Bundle.Case_progressMessage_preparing());
727  try {
728  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
729  deleteCase(metadata, progressIndicator);
730  } else {
731  /*
732  * First, acquire an exclusive case directory lock. The case
733  * cannot be deleted if another node has it open.
734  */
735  progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
737  assert (null != dirLock);
738  deleteCase(metadata, progressIndicator);
739  } catch (CoordinationServiceException ex) {
740  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
741  }
742  }
743  } finally {
744  progressIndicator.finish();
745  }
746  }
747 
758  @Messages({
759  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
760  })
761  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
762  synchronized (caseActionSerializationLock) {
763  if (null != currentCase) {
764  try {
766  } catch (CaseActionException ex) {
767  /*
768  * Notify the user and continue (the error has already been
769  * logged in closeCurrentCase.
770  */
771  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
772  }
773  }
774  try {
775  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
776  newCurrentCase.open(isNewCase);
777  currentCase = newCurrentCase;
778  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
780  updateGUIForCaseOpened(newCurrentCase);
781  }
782  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
783  } catch (CaseActionCancelledException ex) {
784  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
785  throw ex;
786  } catch (CaseActionException ex) {
787  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
788  throw ex;
789  }
790  }
791  }
792 
801  private static String displayNameToUniqueName(String caseDisplayName) {
802  /*
803  * Replace all non-ASCII characters.
804  */
805  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
806 
807  /*
808  * Replace all control characters.
809  */
810  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
811 
812  /*
813  * Replace /, \, :, ?, space, ' ".
814  */
815  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
816 
817  /*
818  * Make it all lowercase.
819  */
820  uniqueCaseName = uniqueCaseName.toLowerCase();
821 
822  /*
823  * Add a time stamp for uniqueness.
824  */
825  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
826  Date date = new Date();
827  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
828 
829  return uniqueCaseName;
830  }
831 
840  public static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
841 
842  File caseDirF = new File(caseDir);
843 
844  if (caseDirF.exists()) {
845  if (caseDirF.isFile()) {
846  throw new CaseActionException(
847  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
848 
849  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
850  throw new CaseActionException(
851  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
852  }
853  }
854 
855  try {
856  boolean result = (caseDirF).mkdirs(); // create root case Directory
857 
858  if (result == false) {
859  throw new CaseActionException(
860  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
861  }
862 
863  // create the folders inside the case directory
864  String hostClause = "";
865 
866  if (caseType == CaseType.MULTI_USER_CASE) {
867  hostClause = File.separator + NetworkUtils.getLocalHostName();
868  }
869  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
870  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
871  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
872  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
873 
874  if (result == false) {
875  throw new CaseActionException(
876  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
877  }
878 
879  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
880  result = new File(modulesOutDir).mkdir();
881 
882  if (result == false) {
883  throw new CaseActionException(
884  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
885  modulesOutDir));
886  }
887 
888  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
889  result = new File(reportsOutDir).mkdir();
890 
891  if (result == false) {
892  throw new CaseActionException(
893  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
894  modulesOutDir));
895 
896  }
897 
898  } catch (MissingResourceException | CaseActionException e) {
899  throw new CaseActionException(
900  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
901  }
902  }
903 
911  static Map<Long, String> getImagePaths(SleuthkitCase db) {
912  Map<Long, String> imgPaths = new HashMap<>();
913  try {
914  Map<Long, List<String>> imgPathsList = db.getImagePaths();
915  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
916  if (entry.getValue().size() > 0) {
917  imgPaths.put(entry.getKey(), entry.getValue().get(0));
918  }
919  }
920  } catch (TskCoreException ex) {
921  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
922  }
923  return imgPaths;
924  }
925 
942  @Messages({
943  "Case.progressMessage.deletingTextIndex=Deleting text index...",
944  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
945  "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
946  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
947  })
948  private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
949  boolean errorsOccurred = false;
950  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
951  /*
952  * Delete the case database from the database server.
953  */
954  try {
955  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
956  CaseDbConnectionInfo db;
958  Class.forName("org.postgresql.Driver"); //NON-NLS
959  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
960  Statement statement = connection.createStatement();) {
961  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
962  statement.execute(deleteCommand);
963  }
964  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
965  logger.log(Level.SEVERE, String.format("Failed to delete case database %s for %s (%s) in %s", metadata.getCaseDatabaseName(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
966  errorsOccurred = true;
967  }
968  }
969 
970  /*
971  * Delete the text index.
972  */
973  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
974  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
975  try {
976  searchService.deleteTextIndex(metadata);
977  } catch (KeywordSearchServiceException ex) {
978  logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
979  errorsOccurred = true;
980  }
981  }
982 
983  /*
984  * Delete the case directory.
985  */
986  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
987  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
988  logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
989  errorsOccurred = true;
990  }
991 
992  /*
993  * If running in a GUI, remove the case from the Recent Cases menu
994  */
996  SwingUtilities.invokeLater(() -> {
997  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
998  });
999  }
1000 
1001  if (errorsOccurred) {
1002  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
1003  }
1004  }
1005 
1016  @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
1017  private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
1018  try {
1019  String resourcesNodeName = caseDir + "_resources";
1020  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
1021  if (null == lock) {
1022  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
1023  }
1024  return lock;
1025  } catch (InterruptedException ex) {
1026  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1027  } catch (CoordinationServiceException ex) {
1028  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1029  }
1030  }
1031 
1032  private static String getNameForTitle() {
1033  //Method should become unnecessary once technical debt story 3334 is done.
1034  if (UserPreferences.getAppName().equals(Version.getName())) {
1035  //Available version number is version number for this application
1036  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1037  } else {
1038  return UserPreferences.getAppName();
1039  }
1040  }
1041 
1045  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1047  SwingUtilities.invokeLater(() -> {
1048  /*
1049  * If the case database was upgraded for a new schema and a
1050  * backup database was created, notify the user.
1051  */
1052  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1053  String backupDbPath = caseDb.getBackupDatabasePath();
1054  if (null != backupDbPath) {
1055  JOptionPane.showMessageDialog(
1056  mainFrame,
1057  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1058  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1059  JOptionPane.INFORMATION_MESSAGE);
1060  }
1061 
1062  /*
1063  * Look for the files for the data sources listed in the case
1064  * database and give the user the opportunity to locate any that
1065  * are missing.
1066  */
1067  Map<Long, String> imgPaths = getImagePaths(caseDb);
1068  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1069  long obj_id = entry.getKey();
1070  String path = entry.getValue();
1071  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1072  if (!fileExists) {
1073  int response = JOptionPane.showConfirmDialog(
1074  mainFrame,
1075  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1076  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1077  JOptionPane.YES_NO_OPTION);
1078  if (response == JOptionPane.YES_OPTION) {
1079  MissingImageDialog.makeDialog(obj_id, caseDb);
1080  } else {
1081  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1082 
1083  }
1084  }
1085  }
1086 
1087  /*
1088  * Enable the case-specific actions.
1089  */
1090  CallableSystemAction.get(AddImageAction.class).setEnabled(true);
1091  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1092  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
1093  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
1094  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1095  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1096  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1097  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1098 
1099  /*
1100  * Add the case to the recent cases tracker that supplies a list
1101  * of recent cases to the recent cases menu item and the
1102  * open/create case dialog.
1103  */
1104  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1105 
1106  /*
1107  * Open the top components (windows within the main application
1108  * window).
1109  *
1110  * Note: If the core windows are not opened here, they will be
1111  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1112  * method on a DATA_SOURCE_ADDED event.
1113  */
1114  if (newCurrentCase.hasData()) {
1116  }
1117 
1118  /*
1119  * Reset the main window title to:
1120  *
1121  * [curent case display name] - [application name].
1122  */
1123  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1124  });
1125  }
1126  }
1127 
1128  /*
1129  * Update the GUI to to reflect the lack of a current case.
1130  */
1131  private static void updateGUIForCaseClosed() {
1133  SwingUtilities.invokeLater(() -> {
1134  /*
1135  * Close the top components (windows within the main application
1136  * window).
1137  */
1139 
1140  /*
1141  * Disable the case-specific menu items.
1142  */
1143  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1144  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1145  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false);
1146  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1147  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1148  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1149  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1150  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1151 
1152  /*
1153  * Clear the notifications in the notfier component in the lower
1154  * right hand corner of the main application window.
1155  */
1157 
1158  /*
1159  * Reset the main window title to be just the application name,
1160  * instead of [curent case display name] - [application name].
1161  */
1162  mainFrame.setTitle(getNameForTitle());
1163  });
1164  }
1165  }
1166 
1170  private static void clearTempSubDir(String tempSubDirPath) {
1171  File tempFolder = new File(tempSubDirPath);
1172  if (tempFolder.isDirectory()) {
1173  File[] files = tempFolder.listFiles();
1174  if (files.length > 0) {
1175  for (File file : files) {
1176  if (file.isDirectory()) {
1177  FileUtil.deleteDir(file);
1178  } else {
1179  file.delete();
1180  }
1181  }
1182  }
1183  }
1184  }
1185 
1191  public SleuthkitCase getSleuthkitCase() {
1192  return this.caseDb;
1193  }
1194 
1201  return caseServices;
1202  }
1203 
1210  return metadata.getCaseType();
1211  }
1212 
1218  public String getCreatedDate() {
1219  return metadata.getCreatedDate();
1220  }
1221 
1227  public String getName() {
1228  return metadata.getCaseName();
1229  }
1230 
1236  public String getDisplayName() {
1237  return metadata.getCaseDisplayName();
1238  }
1239 
1245  public String getNumber() {
1246  return metadata.getCaseNumber();
1247  }
1248 
1254  public String getExaminer() {
1255  return metadata.getExaminer();
1256  }
1257 
1263  public String getExaminerPhone() {
1264  return metadata.getExaminerPhone();
1265  }
1266 
1272  public String getExaminerEmail() {
1273  return metadata.getExaminerEmail();
1274  }
1275 
1281  public String getCaseNotes() {
1282  return metadata.getCaseNotes();
1283  }
1284 
1290  public String getCaseDirectory() {
1291  return metadata.getCaseDirectory();
1292  }
1293 
1302  public String getOutputDirectory() {
1303  String caseDirectory = getCaseDirectory();
1304  Path hostPath;
1305  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1306  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1307  } else {
1308  hostPath = Paths.get(caseDirectory);
1309  }
1310  if (!hostPath.toFile().exists()) {
1311  hostPath.toFile().mkdirs();
1312  }
1313  return hostPath.toString();
1314  }
1315 
1322  public String getTempDirectory() {
1323  return getOrCreateSubdirectory(TEMP_FOLDER);
1324  }
1325 
1332  public String getCacheDirectory() {
1333  return getOrCreateSubdirectory(CACHE_FOLDER);
1334  }
1335 
1342  public String getExportDirectory() {
1343  return getOrCreateSubdirectory(EXPORT_FOLDER);
1344  }
1345 
1352  public String getLogDirectoryPath() {
1353  return getOrCreateSubdirectory(LOG_FOLDER);
1354  }
1355 
1362  public String getReportDirectory() {
1363  return getOrCreateSubdirectory(REPORTS_FOLDER);
1364  }
1365 
1372  public String getConfigDirectory() {
1373  return getOrCreateSubdirectory(CONFIG_FOLDER);
1374  }
1375 
1382  public String getModuleDirectory() {
1383  return getOrCreateSubdirectory(MODULE_FOLDER);
1384  }
1385 
1394  Path path = Paths.get(getModuleDirectory());
1396  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1397  } else {
1398  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1399  }
1400  }
1401 
1411  public List<Content> getDataSources() throws TskCoreException {
1412  List<Content> list = caseDb.getRootObjects();
1413  hasDataSources = (list.size() > 0);
1414  return list;
1415  }
1416 
1422  public Set<TimeZone> getTimeZones() {
1423  Set<TimeZone> timezones = new HashSet<>();
1424  try {
1425  for (Content c : getDataSources()) {
1426  final Content dataSource = c.getDataSource();
1427  if ((dataSource != null) && (dataSource instanceof Image)) {
1428  Image image = (Image) dataSource;
1429  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1430  }
1431  }
1432  } catch (TskCoreException ex) {
1433  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1434  }
1435  return timezones;
1436  }
1437 
1444  public String getTextIndexName() {
1445  return getMetadata().getTextIndexName();
1446  }
1447 
1454  public boolean hasData() {
1455  if (!hasDataSources) {
1456  try {
1457  hasDataSources = (getDataSources().size() > 0);
1458  } catch (TskCoreException ex) {
1459  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1460  }
1461  }
1462  return hasDataSources;
1463  }
1464 
1475  public void notifyAddingDataSource(UUID eventId) {
1476  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1477  }
1478 
1489  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1490  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1491  }
1492 
1504  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1505  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1506  }
1507 
1515  public void notifyContentTagAdded(ContentTag newTag) {
1516  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1517  }
1518 
1526  public void notifyContentTagDeleted(ContentTag deletedTag) {
1527  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1528  }
1529 
1537  public void notifyTagDefinitionChanged(String changedTagName) {
1538  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1539  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1540  }
1541 
1550  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1551  try {
1552  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1553  } catch (NoCurrentCaseException ex) {
1554  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1555  }
1556  }
1557 
1565  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1566  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1567  }
1568 
1576  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1577  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1578  }
1579 
1591  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1592  addReport(localPath, srcModuleName, reportName, null);
1593  }
1594 
1609  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1610  String normalizedLocalPath;
1611  try {
1612  if (localPath.toLowerCase().contains("http:")) {
1613  normalizedLocalPath = localPath;
1614  } else {
1615  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1616  }
1617  } catch (InvalidPathException ex) {
1618  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1619  throw new TskCoreException(errorMsg, ex);
1620  }
1621  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1622  eventPublisher.publish(new ReportAddedEvent(report));
1623  return report;
1624  }
1625 
1634  public List<Report> getAllReports() throws TskCoreException {
1635  return this.caseDb.getAllReports();
1636  }
1637 
1646  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1647  for (Report report : reports) {
1648  this.caseDb.deleteReport(report);
1649  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1650  }
1651  }
1652 
1658  CaseMetadata getMetadata() {
1659  return metadata;
1660  }
1661 
1669  @Messages({
1670  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1671  })
1672  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1673  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1674  try {
1675  metadata.setCaseDetails(caseDetails);
1676  } catch (CaseMetadataException ex) {
1677  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1678  }
1679  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1680  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1681  }
1682  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1683  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1684  }
1685  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1686  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1687  }
1688  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1689  if (RuntimeProperties.runningWithGUI()) {
1690  SwingUtilities.invokeLater(() -> {
1691  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1692  try {
1693  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1694  } catch (Exception ex) {
1695  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1696  }
1697  });
1698  }
1699  }
1700 
1713  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1714  metadata = new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails);
1715  }
1716 
1722  private Case(CaseMetadata caseMetaData) {
1723  metadata = caseMetaData;
1724  }
1725 
1741  @Messages({
1742  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1743  "Case.progressIndicatorTitle.openingCase=Opening Case",
1744  "Case.progressIndicatorCancelButton.label=Cancel",
1745  "Case.progressMessage.preparing=Preparing...",
1746  "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1747  "Case.progressMessage.cancelling=Cancelling...",
1748  "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1749  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1750  })
1751  private void open(boolean isNewCase) throws CaseActionException {
1752  /*
1753  * Create and start either a GUI progress indicator with a Cancel button
1754  * or a logging progress indicator.
1755  */
1756  CancelButtonListener cancelButtonListener = null;
1757  ProgressIndicator progressIndicator;
1759  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1760  String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1761  progressIndicator = new ModalDialogProgressIndicator(
1762  mainFrame,
1763  progressIndicatorTitle,
1764  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1765  Bundle.Case_progressIndicatorCancelButton_label(),
1766  cancelButtonListener);
1767  } else {
1768  progressIndicator = new LoggingProgressIndicator();
1769  }
1770  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1771 
1772  /*
1773  * Creating/opening a case is always done by creating a task running in
1774  * the same non-UI thread that will be used to close the case, so a
1775  * single-threaded executor service is created here and saved as case
1776  * state (must be volatile for cancellation to work).
1777  *
1778  * --- If the case is a single-user case, this supports cancelling
1779  * opening of the case by cancelling the task.
1780  *
1781  * --- If the case is a multi-user case, this still supports
1782  * cancellation, but it also makes it possible for the shared case
1783  * directory lock held as long as the case is open to be released in the
1784  * same thread in which it was acquired, as is required by the
1785  * coordination service.
1786  */
1787  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1788  caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1789  Future<Void> future = caseLockingExecutor.submit(() -> {
1790  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1791  open(isNewCase, progressIndicator);
1792  } else {
1793  /*
1794  * First, acquire a shared case directory lock that will be held
1795  * as long as this node has this case open. This will prevent
1796  * deletion of the case by another node. Next, acquire an
1797  * exclusive case resources lock to ensure only one node at a
1798  * time can create/open/upgrade/close the case resources.
1799  */
1800  progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1803  assert (null != resourcesLock);
1804  open(isNewCase, progressIndicator);
1805  } catch (CaseActionException ex) {
1806  releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
1807  throw ex;
1808  }
1809  }
1810  return null;
1811  });
1812  if (null != cancelButtonListener) {
1813  cancelButtonListener.setCaseActionFuture(future);
1814  }
1815 
1816  /*
1817  * Wait for the case creation/opening task to finish.
1818  */
1819  try {
1820  future.get();
1821  } catch (InterruptedException discarded) {
1822  /*
1823  * The thread this method is running in has been interrupted. Cancel
1824  * the create/open task, wait for it to finish, and shut down the
1825  * executor. This can be done safely because if the task is
1826  * completed with a cancellation condition, the case will have been
1827  * closed and the case directory lock released will have been
1828  * released.
1829  */
1830  if (null != cancelButtonListener) {
1831  cancelButtonListener.actionPerformed(null);
1832  } else {
1833  future.cancel(true);
1834  }
1835  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1836  } catch (CancellationException discarded) {
1837  /*
1838  * The create/open task has been cancelled. Wait for it to finish,
1839  * and shut down the executor. This can be done safely because if
1840  * the task is completed with a cancellation condition, the case
1841  * will have been closed and the case directory lock released will
1842  * have been released.
1843  */
1844  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1845  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1846  } catch (ExecutionException ex) {
1847  /*
1848  * The create/open task has thrown an exception. Wait for it to
1849  * finish, and shut down the executor. This can be done safely
1850  * because if the task is completed with an execution condition, the
1851  * case will have been closed and the case directory lock released
1852  * will have been released.
1853  */
1854  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1855  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1856  } finally {
1857  progressIndicator.finish();
1858  }
1859  }
1860 
1872  private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
1873  try {
1874  if (Thread.currentThread().isInterrupted()) {
1875  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1876  }
1877 
1878  if (isNewCase) {
1879  createCaseData(progressIndicator);
1880  } else {
1881  openCaseData(progressIndicator);
1882  }
1883 
1884  if (Thread.currentThread().isInterrupted()) {
1885  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1886  }
1887 
1888  openServices(progressIndicator);
1889 
1890  if (Thread.currentThread().isInterrupted()) {
1891  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1892  }
1893  } catch (CaseActionException ex) {
1894  /*
1895  * Cancellation or failure. Clean up. The sleep is a little hack to
1896  * clear the interrupted flag for this thread if this is a
1897  * cancellation scenario, so that the clean up can run to completion
1898  * in this thread.
1899  */
1900  try {
1901  Thread.sleep(1);
1902  } catch (InterruptedException discarded) {
1903  }
1904  close(progressIndicator);
1905  throw ex;
1906  }
1907  }
1908 
1919  @Messages({
1920  "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1921  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1922  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}",
1923  "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
1924  })
1925  private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1926  /*
1927  * Create the case directory, if it does not already exist.
1928  *
1929  * TODO (JIRA-2180): Always create the case directory as part of the
1930  * case creation process.
1931  */
1932  if (new File(metadata.getCaseDirectory()).exists() == false) {
1933  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1934  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
1935  }
1936 
1937  /*
1938  * Create the case database.
1939  */
1940  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1941  try {
1942  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1943  /*
1944  * For single-user cases, the case database is a SQLite database
1945  * with a standard name, physically located in the root of the
1946  * case directory.
1947  */
1948  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
1949  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
1950  } else {
1951  /*
1952  * For multi-user cases, the case database is a PostgreSQL
1953  * database with a name derived from the case display name,
1954  * physically located on a database server.
1955  */
1956  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1957  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
1958  }
1959  } catch (TskCoreException ex) {
1960  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
1961  } catch (UserPreferencesException ex) {
1962  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1963  } catch (CaseMetadataException ex) {
1964  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1965  }
1966  }
1967 
1978  @Messages({
1979  "Case.progressMessage.openingCaseDatabase=Opening case database...",
1980  "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database.",
1981  "Case.unsupportedSchemaVersionMessage=Unsupported DB schema version - see log for details",
1982  "Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-User.",
1983  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. "
1984  + "See Tools, Options, Multi-user."
1985  })
1986  private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1987  try {
1988  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
1989  String databaseName = metadata.getCaseDatabaseName();
1990  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1991  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
1993  try {
1994  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1995  } catch (UserPreferencesException ex) {
1996  throw new CaseActionException(Case_databaseConnectionInfo_error_msg(), ex);
1997  }
1998  } else {
1999  throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled());
2000  }
2001  } catch (TskUnsupportedSchemaVersionException ex) {
2002  throw new CaseActionException(Bundle.Case_unsupportedSchemaVersionMessage(), ex);
2003  } catch (TskCoreException ex) {
2004  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
2005  }
2006  }
2007 
2016  @Messages({
2017  "Case.progressMessage.switchingLogDirectory=Switching log directory...",
2018  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
2019  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
2020  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2021  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
2022  private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
2023  /*
2024  * Switch to writing to the application logs in the logs subdirectory of
2025  * the case directory.
2026  */
2027  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2029  if (Thread.currentThread().isInterrupted()) {
2030  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2031  }
2032 
2033  /*
2034  * Clear the temp subdirectory of the case directory.
2035  */
2036  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2038  if (Thread.currentThread().isInterrupted()) {
2039  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2040  }
2041 
2042  /*
2043  * Open the case-level services.
2044  */
2045  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2046  this.caseServices = new Services(caseDb);
2047  if (Thread.currentThread().isInterrupted()) {
2048  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2049  }
2050 
2051  /*
2052  * Allow any registered application services to open any resources
2053  * specific to this case.
2054  */
2055  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2057  if (Thread.currentThread().isInterrupted()) {
2058  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2059  }
2060 
2061  /*
2062  * If this case is a multi-user case, set up for communication with
2063  * other nodes.
2064  */
2065  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2066  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2067  try {
2068  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2069  if (Thread.currentThread().isInterrupted()) {
2070  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2071  }
2072  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2073  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
2074  /*
2075  * The collaboration monitor and event channel are not
2076  * essential. Log an error and notify the user, but do not
2077  * throw.
2078  */
2079  logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
2081  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2082  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"),
2083  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
2084  }
2085  }
2086  }
2087  }
2088 
2093  @NbBundle.Messages({
2094  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2095  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2096  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2097  })
2098  private void openAppServiceCaseResources() throws CaseActionException {
2099  /*
2100  * Each service gets its own independently cancellable/interruptible
2101  * task, running in a named thread managed by an executor service, with
2102  * its own progress indicator. This allows for cancellation of the
2103  * opening of case resources for individual services. It also makes it
2104  * possible to ensure that each service task completes before the next
2105  * one starts by awaiting termination of the executor service.
2106  */
2107  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2108  /*
2109  * Create a progress indicator for the task and start the task. If
2110  * running with a GUI, the progress indicator will be a dialog box
2111  * with a Cancel button.
2112  */
2113  CancelButtonListener cancelButtonListener = null;
2114  ProgressIndicator progressIndicator;
2116  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2117  progressIndicator = new ModalDialogProgressIndicator(
2118  mainFrame,
2119  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2120  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2121  Bundle.Case_progressIndicatorCancelButton_label(),
2122  cancelButtonListener);
2123  } else {
2124  progressIndicator = new LoggingProgressIndicator();
2125  }
2126  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2127  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2128  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2129  threadNameSuffix = threadNameSuffix.toLowerCase();
2130  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2131  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2132  Future<Void> future = executor.submit(() -> {
2133  service.openCaseResources(context);
2134  return null;
2135  });
2136  if (null != cancelButtonListener) {
2137  cancelButtonListener.setCaseContext(context);
2138  cancelButtonListener.setCaseActionFuture(future);
2139  }
2140 
2141  /*
2142  * Wait for the task to either be completed or
2143  * cancelled/interrupted, or for the opening of the case to be
2144  * cancelled.
2145  */
2146  try {
2147  future.get();
2148  } catch (InterruptedException discarded) {
2149  /*
2150  * The parent create/open case task has been cancelled.
2151  */
2152  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()));
2153  future.cancel(true);
2154  } catch (CancellationException discarded) {
2155  /*
2156  * The opening of case resources by the application service has
2157  * been cancelled, so the executor service has thrown. Note that
2158  * there is no guarantee the task itself has responded to the
2159  * cancellation request yet.
2160  */
2161  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()));
2162  } catch (ExecutionException ex) {
2163  /*
2164  * An exception was thrown while executing the task. The
2165  * case-specific application service resources are not
2166  * essential. Log an error and notify the user if running the
2167  * desktop GUI, but do not throw.
2168  */
2169  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2171  SwingUtilities.invokeLater(() -> {
2172  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2173  });
2174  }
2175  } finally {
2176  /*
2177  * Shut down the executor service and wait for it to finish.
2178  * This ensures that the task has finished. Without this, it
2179  * would be possible to start the next task before the current
2180  * task responded to a cancellation request.
2181  */
2183  progressIndicator.finish();
2184  }
2185 
2186  if (Thread.currentThread().isInterrupted()) {
2187  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2188  }
2189  }
2190  }
2191 
2195  private void close() throws CaseActionException {
2196  /*
2197  * Set up either a GUI progress indicator without a Cancel button or a
2198  * logging progress indicator.
2199  */
2200  ProgressIndicator progressIndicator;
2202  progressIndicator = new ModalDialogProgressIndicator(
2203  mainFrame,
2204  Bundle.Case_progressIndicatorTitle_closingCase());
2205  } else {
2206  progressIndicator = new LoggingProgressIndicator();
2207  }
2208  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2209 
2210  /*
2211  * Closing a case is always done in the same non-UI thread that
2212  * opened/created the case. If the case is a multi-user case, this
2213  * ensures that case directory lock that is held as long as the case is
2214  * open is released in the same thread in which it was acquired, as is
2215  * required by the coordination service.
2216  */
2217  Future<Void> future = caseLockingExecutor.submit(() -> {
2218  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2219  close(progressIndicator);
2220  } else {
2221  /*
2222  * Acquire an exclusive case resources lock to ensure only one
2223  * node at a time can create/open/upgrade/close the case
2224  * resources.
2225  */
2226  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2228  assert (null != resourcesLock);
2229  close(progressIndicator);
2230  } finally {
2231  /*
2232  * Always release the case directory lock that was acquired
2233  * when the case was opened.
2234  */
2236  }
2237  }
2238  return null;
2239  });
2240 
2241  try {
2242  future.get();
2243  } catch (InterruptedException | CancellationException unused) {
2244  /*
2245  * The wait has been interrupted by interrupting the thread running
2246  * this method. Not allowing cancellation of case closing, so ignore
2247  * the interrupt. Likewsie, cancellation of the case closing task is
2248  * not supported.
2249  */
2250  } catch (ExecutionException ex) {
2251  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2252  } finally {
2253  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
2254  progressIndicator.finish();
2255  }
2256  }
2257 
2263  @Messages({
2264  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2265  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2266  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2267  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2268  })
2269  private void close(ProgressIndicator progressIndicator) {
2271 
2272  /*
2273  * Stop sending/receiving case events to and from other nodes if this is
2274  * a multi-user case.
2275  */
2276  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2277  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2278  if (null != collaborationMonitor) {
2279  collaborationMonitor.shutdown();
2280  }
2281  eventPublisher.closeRemoteEventChannel();
2282  }
2283 
2284  /*
2285  * Allow all registered application services providers to close
2286  * resources related to the case.
2287  */
2288  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2290 
2291  /*
2292  * Close the case-level services.
2293  */
2294  if (null != caseServices) {
2295  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2296  try {
2297  this.caseServices.close();
2298  } catch (IOException ex) {
2299  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2300  }
2301  }
2302 
2303  /*
2304  * Close the case database
2305  */
2306  if (null != caseDb) {
2307  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2308  caseDb.close();
2309  }
2310 
2311  /*
2312  * Switch the log directory.
2313  */
2314  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2316  }
2317 
2322  @Messages({
2323  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2324  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2325  })
2327  /*
2328  * Each service gets its own independently cancellable task, and thus
2329  * its own task progress indicator.
2330  */
2331  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2332  ProgressIndicator progressIndicator;
2334  progressIndicator = new ModalDialogProgressIndicator(
2335  mainFrame,
2336  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2337  } else {
2338  progressIndicator = new LoggingProgressIndicator();
2339  }
2340  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2341  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2342  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2343  threadNameSuffix = threadNameSuffix.toLowerCase();
2344  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2345  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2346  Future<Void> future = executor.submit(() -> {
2347  service.closeCaseResources(context);
2348  return null;
2349  });
2350  try {
2351  future.get();
2352  } catch (InterruptedException ex) {
2353  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2354  } catch (CancellationException ex) {
2355  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2356  } catch (ExecutionException ex) {
2357  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2359  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2360  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2361  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2362  }
2363  } finally {
2365  progressIndicator.finish();
2366  }
2367  }
2368  }
2369 
2378  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2379  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2380  try {
2381  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2382  if (null == caseDirLock) {
2383  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2384  }
2385  } catch (InterruptedException | CoordinationServiceException ex) {
2386  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2387  }
2388  }
2389 
2395  private void releaseSharedCaseDirLock(String caseDir) {
2396  if (caseDirLock != null) {
2397  try {
2398  caseDirLock.release();
2399  caseDirLock = null;
2401  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2402  }
2403  }
2404  }
2405 
2412  private String getOrCreateSubdirectory(String subDirectoryName) {
2413  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2414  if (!subDirectory.exists()) {
2415  subDirectory.mkdirs();
2416  }
2417  return subDirectory.toString();
2418 
2419  }
2420 
2425  @ThreadSafe
2426  private final static class CancelButtonListener implements ActionListener {
2427 
2428  private final String cancellationMessage;
2429  @GuardedBy("this")
2430  private boolean cancelRequested;
2431  @GuardedBy("this")
2433  @GuardedBy("this")
2434  private Future<?> caseActionFuture;
2435 
2444  private CancelButtonListener(String cancellationMessage) {
2445  this.cancellationMessage = cancellationMessage;
2446  }
2447 
2453  private synchronized void setCaseContext(CaseContext caseContext) {
2454  this.caseContext = caseContext;
2455  /*
2456  * If the cancel button has already been pressed, pass the
2457  * cancellation on to the case context.
2458  */
2459  if (cancelRequested) {
2460  cancel();
2461  }
2462  }
2463 
2469  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
2470  this.caseActionFuture = caseActionFuture;
2471  /*
2472  * If the cancel button has already been pressed, cancel the Future
2473  * of the task.
2474  */
2475  if (cancelRequested) {
2476  cancel();
2477  }
2478  }
2479 
2485  @Override
2486  public synchronized void actionPerformed(ActionEvent event) {
2487  cancel();
2488  }
2489 
2493  private void cancel() {
2494  /*
2495  * At a minimum, set the cancellation requested flag of this
2496  * listener.
2497  */
2498  this.cancelRequested = true;
2499  if (null != this.caseContext) {
2500  /*
2501  * Set the cancellation request flag and display the
2502  * cancellation message in the progress indicator for the case
2503  * context associated with this listener.
2504  */
2506  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
2507  if (progressIndicator instanceof ModalDialogProgressIndicator) {
2508  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2509  }
2510  }
2511  this.caseContext.requestCancel();
2512  }
2513  if (null != this.caseActionFuture) {
2514  /*
2515  * Cancel the Future of the task associated with this listener.
2516  * Note that the task thread will be interrupted if the task is
2517  * blocked.
2518  */
2519  this.caseActionFuture.cancel(true);
2520  }
2521  }
2522  }
2523 
2527  private static class TaskThreadFactory implements ThreadFactory {
2528 
2529  private final String threadName;
2530 
2531  private TaskThreadFactory(String threadName) {
2532  this.threadName = threadName;
2533  }
2534 
2535  @Override
2536  public Thread newThread(Runnable task) {
2537  return new Thread(task, threadName);
2538  }
2539 
2540  }
2541 
2549  @Deprecated
2550  public static String getAppName() {
2551  return UserPreferences.getAppName();
2552  }
2553 
2573  @Deprecated
2574  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2575  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2576  }
2577 
2598  @Deprecated
2599  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2600  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2601  }
2602 
2614  @Deprecated
2615  public static void open(String caseMetadataFilePath) throws CaseActionException {
2616  openAsCurrentCase(caseMetadataFilePath);
2617  }
2618 
2628  @Deprecated
2629  public void closeCase() throws CaseActionException {
2630  closeCurrentCase();
2631  }
2632 
2638  @Deprecated
2639  public static void invokeStartupDialog() {
2641  }
2642 
2656  @Deprecated
2657  public static String convertTimeZone(String timeZoneId) {
2658  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2659  }
2660 
2670  @Deprecated
2671  public static boolean pathExists(String filePath) {
2672  return new File(filePath).isFile();
2673  }
2674 
2683  @Deprecated
2684  public static String getAutopsyVersion() {
2685  return Version.getVersion();
2686  }
2687 
2695  @Deprecated
2696  public static boolean existsCurrentCase() {
2697  return isCaseOpen();
2698  }
2699 
2709  @Deprecated
2710  public static String getModulesOutputDirRelPath() {
2711  return "ModuleOutput"; //NON-NLS
2712  }
2713 
2723  @Deprecated
2724  public static PropertyChangeSupport
2726  return new PropertyChangeSupport(Case.class
2727  );
2728  }
2729 
2738  @Deprecated
2739  public String getModulesOutputDirAbsPath() {
2740  return getModuleDirectory();
2741  }
2742 
2757  @Deprecated
2758  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
2759  try {
2760  Image newDataSource = caseDb.getImageById(imgId);
2761  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
2762  return newDataSource;
2763  } catch (TskCoreException ex) {
2764  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
2765  }
2766  }
2767 
2775  @Deprecated
2776  public Set<TimeZone> getTimeZone() {
2777  return getTimeZones();
2778  }
2779 
2790  @Deprecated
2791  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
2792  deleteReports(reports);
2793  }
2794 
2795 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:139
List< Content > getDataSources()
Definition: Case.java:1411
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1526
Case(CaseMetadata caseMetaData)
Definition: Case.java:1722
static CaseType fromString(String typeName)
Definition: Case.java:182
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:136
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:545
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1576
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:2758
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:1017
static boolean existsCurrentCase()
Definition: Case.java:2696
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:403
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:138
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:126
static final String EXPORT_FOLDER
Definition: Case.java:130
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1537
static volatile Frame mainFrame
Definition: Case.java:141
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2657
static boolean driveExists(String path)
Definition: DriveUtils.java:66
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1550
static final String CACHE_FOLDER
Definition: Case.java:129
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1713
void createCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1925
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1591
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1045
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2599
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:127
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:709
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:2791
volatile ExecutorService caseLockingExecutor
Definition: Case.java:144
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:2453
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1170
static boolean isValidName(String caseName)
Definition: Case.java:487
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2379
CollaborationMonitor collaborationMonitor
Definition: Case.java:147
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2395
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:2710
void openCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1986
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:2486
static final String MODULE_FOLDER
Definition: Case.java:135
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2574
synchronized void openRemoteEventChannel(String channelName)
void openServices(ProgressIndicator progressIndicator)
Definition: Case.java:2022
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:801
void open(boolean isNewCase)
Definition: Case.java:1751
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:572
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:463
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:125
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2269
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1565
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2725
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:391
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2469
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:453
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1646
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1504
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1609
static boolean pathExists(String filePath)
Definition: Case.java:2671
static void open(String caseMetadataFilePath)
Definition: Case.java:2615
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:761
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2412
boolean equalsName(String otherTypeName)
Definition: Case.java:240
static final String EVENT_CHANNEL_NAME
Definition: Case.java:128
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:428
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:131
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:142
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1475
CoordinationService.Lock caseDirLock
Definition: Case.java:145
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:443
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1515
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:137
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:518
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1489
static final String CONFIG_FOLDER
Definition: Case.java:133
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:473
static final Object caseActionSerializationLock
Definition: Case.java:140
void open(boolean isNewCase, ProgressIndicator progressIndicator)
Definition: Case.java:1872
static final String REPORTS_FOLDER
Definition: Case.java:132
static void createCaseDirectory(String caseDir, CaseType caseType)
Definition: Case.java:840
static final String TEMP_FOLDER
Definition: Case.java:134
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:418
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:948

Copyright © 2012-2018 Basis Technology. Generated on: Thu Oct 4 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.