Autopsy  4.7.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.*;
109 import org.sleuthkit.datamodel.BlackboardArtifactTag;
110 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
111 import org.sleuthkit.datamodel.Content;
112 import org.sleuthkit.datamodel.ContentTag;
113 import org.sleuthkit.datamodel.Image;
114 import org.sleuthkit.datamodel.Report;
115 import org.sleuthkit.datamodel.SleuthkitCase;
116 import org.sleuthkit.datamodel.TskCoreException;
117 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
118 
122 public class Case {
123 
124  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
125  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
126  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
127  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
128  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
129  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
130  private static final String LOG_FOLDER = "Log"; //NON-NLS
131  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
132  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
133  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
134  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
135  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
136  private static final Logger logger = Logger.getLogger(Case.class.getName());
138  private static final Object caseActionSerializationLock = new Object();
139  private static volatile Frame mainFrame;
140  private static volatile Case currentCase;
141  private final CaseMetadata metadata;
142  private volatile ExecutorService caseLockingExecutor;
144  private SleuthkitCase caseDb;
145  private CollaborationMonitor collaborationMonitor;
147  private boolean hasDataSources;
148 
149  /*
150  * Get a reference to the main window of the desktop application to use to
151  * parent pop up dialogs and initialize the application name for use in
152  * changing the main window title.
153  */
154  static {
155  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
156  @Override
157  public void run() {
158  mainFrame = WindowManager.getDefault().getMainWindow();
159  }
160  });
161  }
162 
166  public enum CaseType {
167 
168  SINGLE_USER_CASE("Single-user case"), //NON-NLS
169  MULTI_USER_CASE("Multi-user case"); //NON-NLS
170 
171  private final String typeName;
172 
180  public static CaseType fromString(String typeName) {
181  if (typeName != null) {
182  for (CaseType c : CaseType.values()) {
183  if (typeName.equalsIgnoreCase(c.toString())) {
184  return c;
185  }
186  }
187  }
188  return null;
189  }
190 
196  @Override
197  public String toString() {
198  return typeName;
199  }
200 
206  @Messages({
207  "Case_caseType_singleUser=Single-user case",
208  "Case_caseType_multiUser=Multi-user case"
209  })
211  if (fromString(typeName) == SINGLE_USER_CASE) {
212  return Bundle.Case_caseType_singleUser();
213  } else {
214  return Bundle.Case_caseType_multiUser();
215  }
216  }
217 
223  private CaseType(String typeName) {
224  this.typeName = typeName;
225  }
226 
237  @Deprecated
238  public boolean equalsName(String otherTypeName) {
239  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
240  }
241 
242  };
243 
248  public enum Events {
249 
257  @Deprecated
266  @Deprecated
275  @Deprecated
374 
375  };
376 
383  public static void addPropertyChangeListener(PropertyChangeListener listener) {
384  addEventSubscriber(Stream.of(Events.values())
385  .map(Events::toString)
386  .collect(Collectors.toSet()), listener);
387  }
388 
395  public static void removePropertyChangeListener(PropertyChangeListener listener) {
396  removeEventSubscriber(Stream.of(Events.values())
397  .map(Events::toString)
398  .collect(Collectors.toSet()), listener);
399  }
400 
409  @Deprecated
410  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
411  eventPublisher.addSubscriber(eventNames, subscriber);
412  }
413 
420  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
421  eventTypes.forEach((Events event) -> {
422  eventPublisher.addSubscriber(event.toString(), subscriber);
423  });
424  }
425 
434  @Deprecated
435  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
436  eventPublisher.addSubscriber(eventName, subscriber);
437  }
438 
445  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
446  eventPublisher.removeSubscriber(eventName, subscriber);
447  }
448 
455  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
456  eventPublisher.removeSubscriber(eventNames, subscriber);
457  }
458 
465  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
466  eventTypes.forEach((Events event) -> {
467  eventPublisher.removeSubscriber(event.toString(), subscriber);
468  });
469  }
470 
479  public static boolean isValidName(String caseName) {
480  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
481  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
482  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
483  }
484 
509  @Deprecated
510  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
511  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
512  }
513 
533  @Messages({
534  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
535  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
536  })
537  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
538  if (caseDetails.getCaseDisplayName().isEmpty()) {
539  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
540  }
541  if (caseDir.isEmpty()) {
542  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
543  }
544  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
545  }
546 
560  @Messages({
561  "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
562  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
563  })
564  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
566  try {
567  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
568  } catch (CaseMetadataException ex) {
569  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex);
570  }
572  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
573  }
574  openAsCurrentCase(new Case(metadata), false);
575  }
576 
582  public static boolean isCaseOpen() {
583  return currentCase != null;
584  }
585 
593  public static Case getCurrentCase() {
594  try {
595  return getCurrentCaseThrows();
596  } catch (NoCurrentCaseException ex) {
597  /*
598  * Throw a runtime exception, since this is a programming error.
599  */
600  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
601  }
602  }
603 
622  Case openCase = currentCase;
623  if (openCase == null) {
624  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
625  } else {
626  return openCase;
627  }
628  }
629 
638  @Messages({
639  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
640  "Case.progressIndicatorTitle.closingCase=Closing Case"
641  })
642  public static void closeCurrentCase() throws CaseActionException {
643  synchronized (caseActionSerializationLock) {
644  if (null == currentCase) {
645  return;
646  }
647  Case closedCase = currentCase;
648  try {
649  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
650  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
651  currentCase = null;
652  closedCase.close();
653  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
654  } catch (CaseActionException ex) {
655  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
656  throw ex;
657  } finally {
660  }
661  }
662  }
663  }
664 
673  public static void deleteCurrentCase() throws CaseActionException {
674  synchronized (caseActionSerializationLock) {
675  if (null == currentCase) {
676  return;
677  }
678  CaseMetadata metadata = currentCase.getMetadata();
680  deleteCase(metadata);
681  }
682  }
683 
695  @Messages({
696  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
697  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
698  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
699  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
700  })
701  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
702  synchronized (caseActionSerializationLock) {
703  if (null != currentCase) {
704  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
705  }
706  }
707 
708  /*
709  * Set up either a GUI progress indicator without a cancel button (can't
710  * cancel deleting a case) or a logging progress indicator.
711  */
712  ProgressIndicator progressIndicator;
714  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
715  } else {
716  progressIndicator = new LoggingProgressIndicator();
717  }
718  progressIndicator.start(Bundle.Case_progressMessage_preparing());
719  try {
720  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
721  deleteCase(metadata, progressIndicator);
722  } else {
723  /*
724  * First, acquire an exclusive case directory lock. The case
725  * cannot be deleted if another node has it open.
726  */
727  progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
729  assert (null != dirLock);
730  deleteCase(metadata, progressIndicator);
731  } catch (CoordinationServiceException ex) {
732  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
733  }
734  }
735  } finally {
736  progressIndicator.finish();
737  }
738  }
739 
750  @Messages({
751  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
752  })
753  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
754  synchronized (caseActionSerializationLock) {
755  if (null != currentCase) {
756  try {
758  } catch (CaseActionException ex) {
759  /*
760  * Notify the user and continue (the error has already been
761  * logged in closeCurrentCase.
762  */
763  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
764  }
765  }
766  try {
767  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
768  newCurrentCase.open(isNewCase);
769  currentCase = newCurrentCase;
770  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
772  updateGUIForCaseOpened(newCurrentCase);
773  }
774  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
775  } catch (CaseActionCancelledException ex) {
776  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
777  throw ex;
778  } catch (CaseActionException ex) {
779  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
780  throw ex;
781  }
782  }
783  }
784 
793  private static String displayNameToUniqueName(String caseDisplayName) {
794  /*
795  * Replace all non-ASCII characters.
796  */
797  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
798 
799  /*
800  * Replace all control characters.
801  */
802  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
803 
804  /*
805  * Replace /, \, :, ?, space, ' ".
806  */
807  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
808 
809  /*
810  * Make it all lowercase.
811  */
812  uniqueCaseName = uniqueCaseName.toLowerCase();
813 
814  /*
815  * Add a time stamp for uniqueness.
816  */
817  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
818  Date date = new Date();
819  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
820 
821  return uniqueCaseName;
822  }
823 
832  public static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
833 
834  File caseDirF = new File(caseDir);
835 
836  if (caseDirF.exists()) {
837  if (caseDirF.isFile()) {
838  throw new CaseActionException(
839  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
840 
841  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
842  throw new CaseActionException(
843  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
844  }
845  }
846 
847  try {
848  boolean result = (caseDirF).mkdirs(); // create root case Directory
849 
850  if (result == false) {
851  throw new CaseActionException(
852  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
853  }
854 
855  // create the folders inside the case directory
856  String hostClause = "";
857 
858  if (caseType == CaseType.MULTI_USER_CASE) {
859  hostClause = File.separator + NetworkUtils.getLocalHostName();
860  }
861  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
862  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
863  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
864  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
865 
866  if (result == false) {
867  throw new CaseActionException(
868  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
869  }
870 
871  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
872  result = new File(modulesOutDir).mkdir();
873 
874  if (result == false) {
875  throw new CaseActionException(
876  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
877  modulesOutDir));
878  }
879 
880  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
881  result = new File(reportsOutDir).mkdir();
882 
883  if (result == false) {
884  throw new CaseActionException(
885  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
886  modulesOutDir));
887 
888  }
889 
890  } catch (MissingResourceException | CaseActionException e) {
891  throw new CaseActionException(
892  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
893  }
894  }
895 
903  static Map<Long, String> getImagePaths(SleuthkitCase db) {
904  Map<Long, String> imgPaths = new HashMap<>();
905  try {
906  Map<Long, List<String>> imgPathsList = db.getImagePaths();
907  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
908  if (entry.getValue().size() > 0) {
909  imgPaths.put(entry.getKey(), entry.getValue().get(0));
910  }
911  }
912  } catch (TskCoreException ex) {
913  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
914  }
915  return imgPaths;
916  }
917 
934  @Messages({
935  "Case.progressMessage.deletingTextIndex=Deleting text index...",
936  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
937  "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
938  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
939  })
940  private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
941  boolean errorsOccurred = false;
942  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
943  /*
944  * Delete the case database from the database server.
945  */
946  try {
947  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
948  CaseDbConnectionInfo db;
950  Class.forName("org.postgresql.Driver"); //NON-NLS
951  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
952  Statement statement = connection.createStatement();) {
953  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
954  statement.execute(deleteCommand);
955  }
956  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
957  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);
958  errorsOccurred = true;
959  }
960  }
961 
962  /*
963  * Delete the text index.
964  */
965  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
966  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
967  try {
968  searchService.deleteTextIndex(metadata);
969  } catch (KeywordSearchServiceException ex) {
970  logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
971  errorsOccurred = true;
972  }
973  }
974 
975  /*
976  * Delete the case directory.
977  */
978  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
979  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
980  logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
981  errorsOccurred = true;
982  }
983 
984  /*
985  * If running in a GUI, remove the case from the Recent Cases menu
986  */
988  SwingUtilities.invokeLater(() -> {
989  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
990  });
991  }
992 
993  if (errorsOccurred) {
994  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
995  }
996  }
997 
1008  @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
1009  private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
1010  try {
1011  String resourcesNodeName = caseDir + "_resources";
1012  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
1013  if (null == lock) {
1014  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
1015  }
1016  return lock;
1017  } catch (InterruptedException ex) {
1018  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1019  } catch (CoordinationServiceException ex) {
1020  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1021  }
1022  }
1023 
1024  private static String getNameForTitle() {
1025  //Method should become unnecessary once technical debt story 3334 is done.
1026  if (UserPreferences.getAppName().equals(Version.getName())) {
1027  //Available version number is version number for this application
1028  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1029  } else {
1030  return UserPreferences.getAppName();
1031  }
1032  }
1033 
1037  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1039  SwingUtilities.invokeLater(() -> {
1040  /*
1041  * If the case database was upgraded for a new schema and a
1042  * backup database was created, notify the user.
1043  */
1044  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1045  String backupDbPath = caseDb.getBackupDatabasePath();
1046  if (null != backupDbPath) {
1047  JOptionPane.showMessageDialog(
1048  mainFrame,
1049  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1050  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1051  JOptionPane.INFORMATION_MESSAGE);
1052  }
1053 
1054  /*
1055  * Look for the files for the data sources listed in the case
1056  * database and give the user the opportunity to locate any that
1057  * are missing.
1058  */
1059  Map<Long, String> imgPaths = getImagePaths(caseDb);
1060  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1061  long obj_id = entry.getKey();
1062  String path = entry.getValue();
1063  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1064  if (!fileExists) {
1065  int response = JOptionPane.showConfirmDialog(
1066  mainFrame,
1067  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1068  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1069  JOptionPane.YES_NO_OPTION);
1070  if (response == JOptionPane.YES_OPTION) {
1071  MissingImageDialog.makeDialog(obj_id, caseDb);
1072  } else {
1073  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1074 
1075  }
1076  }
1077  }
1078 
1079  /*
1080  * Enable the case-specific actions.
1081  */
1082  CallableSystemAction.get(AddImageAction.class).setEnabled(true);
1083  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1084  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
1085  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
1086  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1087  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1088  CallableSystemAction.get(CommonFilesSearchAction.class).setEnabled(true);
1089  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1090 
1091  /*
1092  * Add the case to the recent cases tracker that supplies a list
1093  * of recent cases to the recent cases menu item and the
1094  * open/create case dialog.
1095  */
1096  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1097 
1098  /*
1099  * Open the top components (windows within the main application
1100  * window).
1101  */
1102  if (newCurrentCase.hasData()) {
1104  }
1105 
1106  /*
1107  * Reset the main window title to:
1108  *
1109  * [curent case display name] - [application name].
1110  */
1111  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1112  });
1113  }
1114  }
1115 
1116  /*
1117  * Update the GUI to to reflect the lack of a current case.
1118  */
1119  private static void updateGUIForCaseClosed() {
1121  SwingUtilities.invokeLater(() -> {
1122  /*
1123  * Close the top components (windows within the main application
1124  * window).
1125  */
1127 
1128  /*
1129  * Disable the case-specific menu items.
1130  */
1131  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1132  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1133  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false);
1134  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1135  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1136  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1137  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1138  CallableSystemAction.get(CommonFilesSearchAction.class).setEnabled(false);
1139 
1140  /*
1141  * Clear the notifications in the notfier component in the lower
1142  * right hand corner of the main application window.
1143  */
1145 
1146  /*
1147  * Reset the main window title to be just the application name,
1148  * instead of [curent case display name] - [application name].
1149  */
1150  mainFrame.setTitle(getNameForTitle());
1151  });
1152  }
1153  }
1154 
1158  private static void clearTempSubDir(String tempSubDirPath) {
1159  File tempFolder = new File(tempSubDirPath);
1160  if (tempFolder.isDirectory()) {
1161  File[] files = tempFolder.listFiles();
1162  if (files.length > 0) {
1163  for (File file : files) {
1164  if (file.isDirectory()) {
1165  FileUtil.deleteDir(file);
1166  } else {
1167  file.delete();
1168  }
1169  }
1170  }
1171  }
1172  }
1173 
1179  public SleuthkitCase getSleuthkitCase() {
1180  return this.caseDb;
1181  }
1182 
1189  return caseServices;
1190  }
1191 
1198  return metadata.getCaseType();
1199  }
1200 
1206  public String getCreatedDate() {
1207  return metadata.getCreatedDate();
1208  }
1209 
1215  public String getName() {
1216  return metadata.getCaseName();
1217  }
1218 
1224  public String getDisplayName() {
1225  return metadata.getCaseDisplayName();
1226  }
1227 
1233  public String getNumber() {
1234  return metadata.getCaseNumber();
1235  }
1236 
1242  public String getExaminer() {
1243  return metadata.getExaminer();
1244  }
1245 
1251  public String getExaminerPhone() {
1252  return metadata.getExaminerPhone();
1253  }
1254 
1260  public String getExaminerEmail() {
1261  return metadata.getExaminerEmail();
1262  }
1263 
1269  public String getCaseNotes() {
1270  return metadata.getCaseNotes();
1271  }
1272 
1278  public String getCaseDirectory() {
1279  return metadata.getCaseDirectory();
1280  }
1281 
1290  public String getOutputDirectory() {
1291  String caseDirectory = getCaseDirectory();
1292  Path hostPath;
1293  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1294  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1295  } else {
1296  hostPath = Paths.get(caseDirectory);
1297  }
1298  if (!hostPath.toFile().exists()) {
1299  hostPath.toFile().mkdirs();
1300  }
1301  return hostPath.toString();
1302  }
1303 
1310  public String getTempDirectory() {
1311  return getOrCreateSubdirectory(TEMP_FOLDER);
1312  }
1313 
1320  public String getCacheDirectory() {
1321  return getOrCreateSubdirectory(CACHE_FOLDER);
1322  }
1323 
1330  public String getExportDirectory() {
1331  return getOrCreateSubdirectory(EXPORT_FOLDER);
1332  }
1333 
1340  public String getLogDirectoryPath() {
1341  return getOrCreateSubdirectory(LOG_FOLDER);
1342  }
1343 
1350  public String getReportDirectory() {
1351  return getOrCreateSubdirectory(REPORTS_FOLDER);
1352  }
1353 
1360  public String getModuleDirectory() {
1361  return getOrCreateSubdirectory(MODULE_FOLDER);
1362  }
1363 
1372  Path path = Paths.get(getModuleDirectory());
1374  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1375  } else {
1376  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1377  }
1378  }
1379 
1389  public List<Content> getDataSources() throws TskCoreException {
1390  List<Content> list = caseDb.getRootObjects();
1391  hasDataSources = (list.size() > 0);
1392  return list;
1393  }
1394 
1400  public Set<TimeZone> getTimeZones() {
1401  Set<TimeZone> timezones = new HashSet<>();
1402  try {
1403  for (Content c : getDataSources()) {
1404  final Content dataSource = c.getDataSource();
1405  if ((dataSource != null) && (dataSource instanceof Image)) {
1406  Image image = (Image) dataSource;
1407  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1408  }
1409  }
1410  } catch (TskCoreException ex) {
1411  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1412  }
1413  return timezones;
1414  }
1415 
1422  public String getTextIndexName() {
1423  return getMetadata().getTextIndexName();
1424  }
1425 
1432  public boolean hasData() {
1433  if (!hasDataSources) {
1434  try {
1435  hasDataSources = (getDataSources().size() > 0);
1436  } catch (TskCoreException ex) {
1437  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1438  }
1439  }
1440  return hasDataSources;
1441  }
1442 
1453  public void notifyAddingDataSource(UUID eventId) {
1454  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1455  }
1456 
1467  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1468  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1469  }
1470 
1482  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1483  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1484  }
1485 
1493  public void notifyContentTagAdded(ContentTag newTag) {
1494  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1495  }
1496 
1504  public void notifyContentTagDeleted(ContentTag deletedTag) {
1505  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1506  }
1507 
1515  public void notifyTagDefinitionChanged(String changedTagName) {
1516  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1517  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1518  }
1519 
1527  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1528  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1529  }
1530 
1538  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1539  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1540  }
1541 
1553  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1554  addReport(localPath, srcModuleName, reportName, null);
1555  }
1556 
1571  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1572  String normalizedLocalPath;
1573  try {
1574  if (localPath.toLowerCase().contains("http:")) {
1575  normalizedLocalPath = localPath;
1576  } else {
1577  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1578  }
1579  } catch (InvalidPathException ex) {
1580  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1581  throw new TskCoreException(errorMsg, ex);
1582  }
1583  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1584  eventPublisher.publish(new ReportAddedEvent(report));
1585  return report;
1586  }
1587 
1596  public List<Report> getAllReports() throws TskCoreException {
1597  return this.caseDb.getAllReports();
1598  }
1599 
1608  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1609  for (Report report : reports) {
1610  this.caseDb.deleteReport(report);
1611  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1612  }
1613  }
1614 
1620  CaseMetadata getMetadata() {
1621  return metadata;
1622  }
1623 
1631  @Messages({
1632  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1633  })
1634  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1635  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1636  try {
1637  metadata.setCaseDetails(caseDetails);
1638  } catch (CaseMetadataException ex) {
1639  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1640  }
1641  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1642  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1643  }
1644  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1645  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1646  }
1647  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1648  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1649  }
1650  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1651  if (RuntimeProperties.runningWithGUI()) {
1652  SwingUtilities.invokeLater(() -> {
1653  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1654  try {
1655  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1656  } catch (Exception ex) {
1657  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1658  }
1659  });
1660  }
1661  }
1662 
1675  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1676  metadata = new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails);
1677  }
1678 
1684  private Case(CaseMetadata caseMetaData) {
1685  metadata = caseMetaData;
1686  }
1687 
1703  @Messages({
1704  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1705  "Case.progressIndicatorTitle.openingCase=Opening Case",
1706  "Case.progressIndicatorCancelButton.label=Cancel",
1707  "Case.progressMessage.preparing=Preparing...",
1708  "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1709  "Case.progressMessage.cancelling=Cancelling...",
1710  "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1711  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1712  })
1713  private void open(boolean isNewCase) throws CaseActionException {
1714  /*
1715  * Create and start either a GUI progress indicator with a Cancel button
1716  * or a logging progress indicator.
1717  */
1718  CancelButtonListener cancelButtonListener = null;
1719  ProgressIndicator progressIndicator;
1721  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1722  String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1723  progressIndicator = new ModalDialogProgressIndicator(
1724  mainFrame,
1725  progressIndicatorTitle,
1726  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1727  Bundle.Case_progressIndicatorCancelButton_label(),
1728  cancelButtonListener);
1729  } else {
1730  progressIndicator = new LoggingProgressIndicator();
1731  }
1732  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1733 
1734  /*
1735  * Creating/opening a case is always done by creating a task running in
1736  * the same non-UI thread that will be used to close the case, so a
1737  * single-threaded executor service is created here and saved as case
1738  * state (must be volatile for cancellation to work).
1739  *
1740  * --- If the case is a single-user case, this supports cancelling
1741  * opening of the case by cancelling the task.
1742  *
1743  * --- If the case is a multi-user case, this still supports
1744  * cancellation, but it also makes it possible for the shared case
1745  * directory lock held as long as the case is open to be released in the
1746  * same thread in which it was acquired, as is required by the
1747  * coordination service.
1748  */
1749  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1750  caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1751  Future<Void> future = caseLockingExecutor.submit(() -> {
1752  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1753  open(isNewCase, progressIndicator);
1754  } else {
1755  /*
1756  * First, acquire a shared case directory lock that will be held
1757  * as long as this node has this case open. This will prevent
1758  * deletion of the case by another node. Next, acquire an
1759  * exclusive case resources lock to ensure only one node at a
1760  * time can create/open/upgrade/close the case resources.
1761  */
1762  progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1765  assert (null != resourcesLock);
1766  open(isNewCase, progressIndicator);
1767  } catch (CaseActionException ex) {
1768  releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
1769  throw ex;
1770  }
1771  }
1772  return null;
1773  });
1774  if (null != cancelButtonListener) {
1775  cancelButtonListener.setCaseActionFuture(future);
1776  }
1777 
1778  /*
1779  * Wait for the case creation/opening task to finish.
1780  */
1781  try {
1782  future.get();
1783  } catch (InterruptedException discarded) {
1784  /*
1785  * The thread this method is running in has been interrupted. Cancel
1786  * the create/open task, wait for it to finish, and shut down the
1787  * executor. This can be done safely because if the task is
1788  * completed with a cancellation condition, the case will have been
1789  * closed and the case directory lock released will have been
1790  * released.
1791  */
1792  if (null != cancelButtonListener) {
1793  cancelButtonListener.actionPerformed(null);
1794  } else {
1795  future.cancel(true);
1796  }
1797  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1798  } catch (CancellationException discarded) {
1799  /*
1800  * The create/open task has been cancelled. Wait for it to finish,
1801  * and shut down the executor. This can be done safely because if
1802  * the task is completed with a cancellation condition, the case
1803  * will have been closed and the case directory lock released will
1804  * have been released.
1805  */
1806  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1807  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1808  } catch (ExecutionException ex) {
1809  /*
1810  * The create/open task has thrown an exception. Wait for it to
1811  * finish, and shut down the executor. This can be done safely
1812  * because if the task is completed with an execution condition, the
1813  * case will have been closed and the case directory lock released
1814  * will have been released.
1815  */
1816  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1817  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1818  } finally {
1819  progressIndicator.finish();
1820  }
1821  }
1822 
1834  private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
1835  try {
1836  if (Thread.currentThread().isInterrupted()) {
1837  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1838  }
1839 
1840  if (isNewCase) {
1841  createCaseData(progressIndicator);
1842  } else {
1843  openCaseData(progressIndicator);
1844  }
1845 
1846  if (Thread.currentThread().isInterrupted()) {
1847  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1848  }
1849 
1850  openServices(progressIndicator);
1851 
1852  if (Thread.currentThread().isInterrupted()) {
1853  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1854  }
1855  } catch (CaseActionException ex) {
1856  /*
1857  * Cancellation or failure. Clean up. The sleep is a little hack to
1858  * clear the interrupted flag for this thread if this is a
1859  * cancellation scenario, so that the clean up can run to completion
1860  * in this thread.
1861  */
1862  try {
1863  Thread.sleep(1);
1864  } catch (InterruptedException discarded) {
1865  }
1866  close(progressIndicator);
1867  throw ex;
1868  }
1869  }
1870 
1881  @Messages({
1882  "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1883  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1884  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}",
1885  "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
1886  })
1887  private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1888  /*
1889  * Create the case directory, if it does not already exist.
1890  *
1891  * TODO (JIRA-2180): Always create the case directory as part of the
1892  * case creation process.
1893  */
1894  if (new File(metadata.getCaseDirectory()).exists() == false) {
1895  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1896  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
1897  }
1898 
1899  /*
1900  * Create the case database.
1901  */
1902  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1903  try {
1904  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1905  /*
1906  * For single-user cases, the case database is a SQLite database
1907  * with a standard name, physically located in the root of the
1908  * case directory.
1909  */
1910  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
1911  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
1912  } else {
1913  /*
1914  * For multi-user cases, the case database is a PostgreSQL
1915  * database with a name derived from the case display name,
1916  * physically located on a database server.
1917  */
1918  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1919  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
1920  }
1921  } catch (TskCoreException ex) {
1922  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
1923  } catch (UserPreferencesException ex) {
1924  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1925  } catch (CaseMetadataException ex) {
1926  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1927  }
1928  }
1929 
1940  @Messages({
1941  "Case.progressMessage.openingCaseDatabase=Opening case database...",
1942  "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database.",
1943  "Case.unsupportedSchemaVersionMessage=Unsupported DB schema version - see log for details",
1944  "Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-User.",
1945  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. "
1946  + "See Tools, Options, Multi-user."
1947  })
1948  private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1949  try {
1950  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
1951  String databaseName = metadata.getCaseDatabaseName();
1952  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1953  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
1955  try {
1956  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1957  } catch (UserPreferencesException ex) {
1958  throw new CaseActionException(Case_databaseConnectionInfo_error_msg(), ex);
1959  }
1960  } else {
1961  throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled());
1962  }
1963  } catch (TskUnsupportedSchemaVersionException ex) {
1964  throw new CaseActionException(Bundle.Case_unsupportedSchemaVersionMessage(), ex);
1965  } catch (TskCoreException ex) {
1966  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
1967  }
1968  }
1969 
1978  @Messages({
1979  "Case.progressMessage.switchingLogDirectory=Switching log directory...",
1980  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
1981  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
1982  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
1983  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
1984  private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
1985  /*
1986  * Switch to writing to the application logs in the logs subdirectory of
1987  * the case directory.
1988  */
1989  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
1991  if (Thread.currentThread().isInterrupted()) {
1992  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1993  }
1994 
1995  /*
1996  * Clear the temp subdirectory of the case directory.
1997  */
1998  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2000  if (Thread.currentThread().isInterrupted()) {
2001  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2002  }
2003 
2004  /*
2005  * Open the case-level services.
2006  */
2007  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2008  this.caseServices = new Services(caseDb);
2009  if (Thread.currentThread().isInterrupted()) {
2010  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2011  }
2012 
2013  /*
2014  * Allow any registered application services to open any resources
2015  * specific to this case.
2016  */
2017  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2019  if (Thread.currentThread().isInterrupted()) {
2020  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2021  }
2022 
2023  /*
2024  * If this case is a multi-user case, set up for communication with
2025  * other nodes.
2026  */
2027  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2028  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2029  try {
2030  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2031  if (Thread.currentThread().isInterrupted()) {
2032  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2033  }
2034  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2035  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
2036  /*
2037  * The collaboration monitor and event channel are not
2038  * essential. Log an error and notify the user, but do not
2039  * throw.
2040  */
2041  logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
2043  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2044  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"),
2045  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
2046  }
2047  }
2048  }
2049  }
2050 
2055  @NbBundle.Messages({
2056  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2057  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2058  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2059  })
2060  private void openAppServiceCaseResources() throws CaseActionException {
2061  /*
2062  * Each service gets its own independently cancellable/interruptible
2063  * task, running in a named thread managed by an executor service, with
2064  * its own progress indicator. This allows for cancellation of the
2065  * opening of case resources for individual services. It also makes it
2066  * possible to ensure that each service task completes before the next
2067  * one starts by awaiting termination of the executor service.
2068  */
2069  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2070  /*
2071  * Create a progress indicator for the task and start the task. If
2072  * running with a GUI, the progress indicator will be a dialog box
2073  * with a Cancel button.
2074  */
2075  CancelButtonListener cancelButtonListener = null;
2076  ProgressIndicator progressIndicator;
2078  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2079  progressIndicator = new ModalDialogProgressIndicator(
2080  mainFrame,
2081  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2082  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2083  Bundle.Case_progressIndicatorCancelButton_label(),
2084  cancelButtonListener);
2085  } else {
2086  progressIndicator = new LoggingProgressIndicator();
2087  }
2088  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2089  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2090  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2091  threadNameSuffix = threadNameSuffix.toLowerCase();
2092  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2093  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2094  Future<Void> future = executor.submit(() -> {
2095  service.openCaseResources(context);
2096  return null;
2097  });
2098  if (null != cancelButtonListener) {
2099  cancelButtonListener.setCaseContext(context);
2100  cancelButtonListener.setCaseActionFuture(future);
2101  }
2102 
2103  /*
2104  * Wait for the task to either be completed or
2105  * cancelled/interrupted, or for the opening of the case to be
2106  * cancelled.
2107  */
2108  try {
2109  future.get();
2110  } catch (InterruptedException discarded) {
2111  /*
2112  * The parent create/open case task has been cancelled.
2113  */
2114  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()));
2115  future.cancel(true);
2116  } catch (CancellationException discarded) {
2117  /*
2118  * The opening of case resources by the application service has
2119  * been cancelled, so the executor service has thrown. Note that
2120  * there is no guarantee the task itself has responded to the
2121  * cancellation request yet.
2122  */
2123  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()));
2124  } catch (ExecutionException ex) {
2125  /*
2126  * An exception was thrown while executing the task. The
2127  * case-specific application service resources are not
2128  * essential. Log an error and notify the user if running the
2129  * desktop GUI, but do not throw.
2130  */
2131  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2133  SwingUtilities.invokeLater(() -> {
2134  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2135  });
2136  }
2137  } finally {
2138  /*
2139  * Shut down the executor service and wait for it to finish.
2140  * This ensures that the task has finished. Without this, it
2141  * would be possible to start the next task before the current
2142  * task responded to a cancellation request.
2143  */
2145  progressIndicator.finish();
2146  }
2147 
2148  if (Thread.currentThread().isInterrupted()) {
2149  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2150  }
2151  }
2152  }
2153 
2157  private void close() throws CaseActionException {
2158  /*
2159  * Set up either a GUI progress indicator without a Cancel button or a
2160  * logging progress indicator.
2161  */
2162  ProgressIndicator progressIndicator;
2164  progressIndicator = new ModalDialogProgressIndicator(
2165  mainFrame,
2166  Bundle.Case_progressIndicatorTitle_closingCase());
2167  } else {
2168  progressIndicator = new LoggingProgressIndicator();
2169  }
2170  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2171 
2172  /*
2173  * Closing a case is always done in the same non-UI thread that
2174  * opened/created the case. If the case is a multi-user case, this
2175  * ensures that case directory lock that is held as long as the case is
2176  * open is released in the same thread in which it was acquired, as is
2177  * required by the coordination service.
2178  */
2179  Future<Void> future = caseLockingExecutor.submit(() -> {
2180  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2181  close(progressIndicator);
2182  } else {
2183  /*
2184  * Acquire an exclusive case resources lock to ensure only one
2185  * node at a time can create/open/upgrade/close the case
2186  * resources.
2187  */
2188  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2190  assert (null != resourcesLock);
2191  close(progressIndicator);
2192  } finally {
2193  /*
2194  * Always release the case directory lock that was acquired
2195  * when the case was opened.
2196  */
2198  }
2199  }
2200  return null;
2201  });
2202 
2203  try {
2204  future.get();
2205  } catch (InterruptedException | CancellationException unused) {
2206  /*
2207  * The wait has been interrupted by interrupting the thread running
2208  * this method. Not allowing cancellation of case closing, so ignore
2209  * the interrupt. Likewsie, cancellation of the case closing task is
2210  * not supported.
2211  */
2212  } catch (ExecutionException ex) {
2213  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2214  } finally {
2215  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
2216  progressIndicator.finish();
2217  }
2218  }
2219 
2225  @Messages({
2226  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2227  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2228  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2229  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2230  })
2231  private void close(ProgressIndicator progressIndicator) {
2233 
2234  /*
2235  * Stop sending/receiving case events to and from other nodes if this is
2236  * a multi-user case.
2237  */
2238  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2239  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2240  if (null != collaborationMonitor) {
2241  collaborationMonitor.shutdown();
2242  }
2243  eventPublisher.closeRemoteEventChannel();
2244  }
2245 
2246  /*
2247  * Allow all registered application services providers to close
2248  * resources related to the case.
2249  */
2250  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2252 
2253  /*
2254  * Close the case-level services.
2255  */
2256  if (null != caseServices) {
2257  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2258  try {
2259  this.caseServices.close();
2260  } catch (IOException ex) {
2261  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2262  }
2263  }
2264 
2265  /*
2266  * Close the case database
2267  */
2268  if (null != caseDb) {
2269  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2270  caseDb.close();
2271  }
2272 
2273  /*
2274  * Switch the log directory.
2275  */
2276  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2278  }
2279 
2284  @Messages({
2285  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2286  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2287  })
2289  /*
2290  * Each service gets its own independently cancellable task, and thus
2291  * its own task progress indicator.
2292  */
2293  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2294  ProgressIndicator progressIndicator;
2296  progressIndicator = new ModalDialogProgressIndicator(
2297  mainFrame,
2298  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2299  } else {
2300  progressIndicator = new LoggingProgressIndicator();
2301  }
2302  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2303  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2304  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2305  threadNameSuffix = threadNameSuffix.toLowerCase();
2306  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2307  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2308  Future<Void> future = executor.submit(() -> {
2309  service.closeCaseResources(context);
2310  return null;
2311  });
2312  try {
2313  future.get();
2314  } catch (InterruptedException ex) {
2315  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2316  } catch (CancellationException ex) {
2317  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2318  } catch (ExecutionException ex) {
2319  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2321  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2322  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2323  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2324  }
2325  } finally {
2327  progressIndicator.finish();
2328  }
2329  }
2330  }
2331 
2340  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2341  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2342  try {
2343  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2344  if (null == caseDirLock) {
2345  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2346  }
2347  } catch (InterruptedException | CoordinationServiceException ex) {
2348  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2349  }
2350  }
2351 
2357  private void releaseSharedCaseDirLock(String caseDir) {
2358  if (caseDirLock != null) {
2359  try {
2360  caseDirLock.release();
2361  caseDirLock = null;
2363  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2364  }
2365  }
2366  }
2367 
2374  private String getOrCreateSubdirectory(String subDirectoryName) {
2375  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2376  if (!subDirectory.exists()) {
2377  subDirectory.mkdirs();
2378  }
2379  return subDirectory.toString();
2380 
2381  }
2382 
2387  @ThreadSafe
2388  private final static class CancelButtonListener implements ActionListener {
2389 
2390  private final String cancellationMessage;
2391  @GuardedBy("this")
2392  private boolean cancelRequested;
2393  @GuardedBy("this")
2395  @GuardedBy("this")
2396  private Future<?> caseActionFuture;
2397 
2406  private CancelButtonListener(String cancellationMessage) {
2407  this.cancellationMessage = cancellationMessage;
2408  }
2409 
2415  private synchronized void setCaseContext(CaseContext caseContext) {
2416  this.caseContext = caseContext;
2417  /*
2418  * If the cancel button has already been pressed, pass the
2419  * cancellation on to the case context.
2420  */
2421  if (cancelRequested) {
2422  cancel();
2423  }
2424  }
2425 
2431  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
2432  this.caseActionFuture = caseActionFuture;
2433  /*
2434  * If the cancel button has already been pressed, cancel the Future
2435  * of the task.
2436  */
2437  if (cancelRequested) {
2438  cancel();
2439  }
2440  }
2441 
2447  @Override
2448  public synchronized void actionPerformed(ActionEvent event) {
2449  cancel();
2450  }
2451 
2455  private void cancel() {
2456  /*
2457  * At a minimum, set the cancellation requested flag of this
2458  * listener.
2459  */
2460  this.cancelRequested = true;
2461  if (null != this.caseContext) {
2462  /*
2463  * Set the cancellation request flag and display the
2464  * cancellation message in the progress indicator for the case
2465  * context associated with this listener.
2466  */
2468  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
2469  if (progressIndicator instanceof ModalDialogProgressIndicator) {
2470  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2471  }
2472  }
2473  this.caseContext.requestCancel();
2474  }
2475  if (null != this.caseActionFuture) {
2476  /*
2477  * Cancel the Future of the task associated with this listener.
2478  * Note that the task thread will be interrupted if the task is
2479  * blocked.
2480  */
2481  this.caseActionFuture.cancel(true);
2482  }
2483  }
2484  }
2485 
2489  private static class TaskThreadFactory implements ThreadFactory {
2490 
2491  private final String threadName;
2492 
2493  private TaskThreadFactory(String threadName) {
2494  this.threadName = threadName;
2495  }
2496 
2497  @Override
2498  public Thread newThread(Runnable task) {
2499  return new Thread(task, threadName);
2500  }
2501 
2502  }
2503 
2511  @Deprecated
2512  public static String getAppName() {
2513  return UserPreferences.getAppName();
2514  }
2515 
2535  @Deprecated
2536  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2537  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2538  }
2539 
2560  @Deprecated
2561  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2562  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2563  }
2564 
2576  @Deprecated
2577  public static void open(String caseMetadataFilePath) throws CaseActionException {
2578  openAsCurrentCase(caseMetadataFilePath);
2579  }
2580 
2590  @Deprecated
2591  public void closeCase() throws CaseActionException {
2592  closeCurrentCase();
2593  }
2594 
2600  @Deprecated
2601  public static void invokeStartupDialog() {
2603  }
2604 
2618  @Deprecated
2619  public static String convertTimeZone(String timeZoneId) {
2620  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2621  }
2622 
2632  @Deprecated
2633  public static boolean pathExists(String filePath) {
2634  return new File(filePath).isFile();
2635  }
2636 
2645  @Deprecated
2646  public static String getAutopsyVersion() {
2647  return Version.getVersion();
2648  }
2649 
2657  @Deprecated
2658  public static boolean existsCurrentCase() {
2659  return isCaseOpen();
2660  }
2661 
2671  @Deprecated
2672  public static String getModulesOutputDirRelPath() {
2673  return "ModuleOutput"; //NON-NLS
2674  }
2675 
2685  @Deprecated
2686  public static PropertyChangeSupport
2688  return new PropertyChangeSupport(Case.class
2689  );
2690  }
2691 
2700  @Deprecated
2701  public String getModulesOutputDirAbsPath() {
2702  return getModuleDirectory();
2703  }
2704 
2719  @Deprecated
2720  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
2721  try {
2722  Image newDataSource = caseDb.getImageById(imgId);
2723  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
2724  return newDataSource;
2725  } catch (TskCoreException ex) {
2726  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
2727  }
2728  }
2729 
2737  @Deprecated
2738  public Set<TimeZone> getTimeZone() {
2739  return getTimeZones();
2740  }
2741 
2752  @Deprecated
2753  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
2754  deleteReports(reports);
2755  }
2756 
2757 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:137
List< Content > getDataSources()
Definition: Case.java:1389
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1504
Case(CaseMetadata caseMetaData)
Definition: Case.java:1684
static CaseType fromString(String typeName)
Definition: Case.java:180
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:134
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:537
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1538
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:2720
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:1009
static boolean existsCurrentCase()
Definition: Case.java:2658
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:395
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:136
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:125
static final String EXPORT_FOLDER
Definition: Case.java:129
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1515
static volatile Frame mainFrame
Definition: Case.java:139
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2619
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
static final String CACHE_FOLDER
Definition: Case.java:128
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1675
void createCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1887
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1553
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1037
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2561
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:126
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:701
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:2753
volatile ExecutorService caseLockingExecutor
Definition: Case.java:142
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:2415
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1158
static boolean isValidName(String caseName)
Definition: Case.java:479
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2341
CollaborationMonitor collaborationMonitor
Definition: Case.java:145
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2357
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:2672
void openCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1948
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:2448
static final String MODULE_FOLDER
Definition: Case.java:133
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2536
synchronized void openRemoteEventChannel(String channelName)
void openServices(ProgressIndicator progressIndicator)
Definition: Case.java:1984
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:793
void open(boolean isNewCase)
Definition: Case.java:1713
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:564
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:455
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:124
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2231
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1527
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2687
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:383
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2431
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:445
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1608
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1482
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1571
static boolean pathExists(String filePath)
Definition: Case.java:2633
static void open(String caseMetadataFilePath)
Definition: Case.java:2577
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:753
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2374
boolean equalsName(String otherTypeName)
Definition: Case.java:238
static final String EVENT_CHANNEL_NAME
Definition: Case.java:127
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:420
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:130
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:140
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1453
CoordinationService.Lock caseDirLock
Definition: Case.java:143
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:435
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1493
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:135
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:510
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1467
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:465
static final Object caseActionSerializationLock
Definition: Case.java:138
void open(boolean isNewCase, ProgressIndicator progressIndicator)
Definition: Case.java:1834
static final String REPORTS_FOLDER
Definition: Case.java:131
static void createCaseDirectory(String caseDir, CaseType caseType)
Definition: Case.java:832
static final String TEMP_FOLDER
Definition: Case.java:132
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:410
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:940

Copyright © 2012-2016 Basis Technology. Generated on: Mon Jun 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.