Autopsy  4.5.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-2017 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.*;
108 import org.sleuthkit.datamodel.BlackboardArtifactTag;
109 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
110 import org.sleuthkit.datamodel.Content;
111 import org.sleuthkit.datamodel.ContentTag;
112 import org.sleuthkit.datamodel.Image;
113 import org.sleuthkit.datamodel.Report;
114 import org.sleuthkit.datamodel.SleuthkitCase;
115 import org.sleuthkit.datamodel.TskCoreException;
116 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
117 
121 public class Case {
122 
123  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
124  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
125  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
126  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
127  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
128  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
129  private static final String LOG_FOLDER = "Log"; //NON-NLS
130  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
131  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
132  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
133  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
134  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
135  private static final Logger logger = Logger.getLogger(Case.class.getName());
137  private static final Object caseActionSerializationLock = new Object();
138  private static volatile Frame mainFrame;
139  private static volatile Case currentCase;
140  private final CaseMetadata metadata;
141  private volatile ExecutorService caseLockingExecutor;
143  private SleuthkitCase caseDb;
144  private CollaborationMonitor collaborationMonitor;
146  private boolean hasDataSources;
147 
148  /*
149  * Get a reference to the main window of the desktop application to use to
150  * parent pop up dialogs and initialize the application name for use in
151  * changing the main window title.
152  */
153  static {
154  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
155  @Override
156  public void run() {
157  mainFrame = WindowManager.getDefault().getMainWindow();
158  }
159  });
160  }
161 
165  public enum CaseType {
166 
167  SINGLE_USER_CASE("Single-user case"), //NON-NLS
168  MULTI_USER_CASE("Multi-user case"); //NON-NLS
169 
170  private final String typeName;
171 
179  public static CaseType fromString(String typeName) {
180  if (typeName != null) {
181  for (CaseType c : CaseType.values()) {
182  if (typeName.equalsIgnoreCase(c.toString())) {
183  return c;
184  }
185  }
186  }
187  return null;
188  }
189 
195  @Override
196  public String toString() {
197  return typeName;
198  }
199 
205  @Messages({
206  "Case_caseType_singleUser=Single-user case",
207  "Case_caseType_multiUser=Multi-user case"
208  })
210  if (fromString(typeName) == SINGLE_USER_CASE) {
211  return Bundle.Case_caseType_singleUser();
212  } else {
213  return Bundle.Case_caseType_multiUser();
214  }
215  }
216 
222  private CaseType(String typeName) {
223  this.typeName = typeName;
224  }
225 
236  @Deprecated
237  public boolean equalsName(String otherTypeName) {
238  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
239  }
240 
241  };
242 
247  public enum Events {
248 
256  @Deprecated
265  @Deprecated
274  @Deprecated
373 
374  };
375 
382  public static void addPropertyChangeListener(PropertyChangeListener listener) {
383  addEventSubscriber(Stream.of(Events.values())
384  .map(Events::toString)
385  .collect(Collectors.toSet()), listener);
386  }
387 
394  public static void removePropertyChangeListener(PropertyChangeListener listener) {
395  removeEventSubscriber(Stream.of(Events.values())
396  .map(Events::toString)
397  .collect(Collectors.toSet()), listener);
398  }
399 
408  @Deprecated
409  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
410  eventPublisher.addSubscriber(eventNames, subscriber);
411  }
412 
419  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
420  eventTypes.forEach((Events event) -> {
421  eventPublisher.addSubscriber(event.toString(), subscriber);
422  });
423  }
424 
433  @Deprecated
434  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
435  eventPublisher.addSubscriber(eventName, subscriber);
436  }
437 
444  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
445  eventPublisher.removeSubscriber(eventName, subscriber);
446  }
447 
454  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
455  eventPublisher.removeSubscriber(eventNames, subscriber);
456  }
457 
464  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
465  eventTypes.forEach((Events event) -> {
466  eventPublisher.removeSubscriber(event.toString(), subscriber);
467  });
468  }
469 
478  public static boolean isValidName(String caseName) {
479  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
480  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
481  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
482  }
483 
508  @Deprecated
509  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
510  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
511  }
512 
532  @Messages({
533  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
534  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
535  })
536  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
537  if (caseDetails.getCaseDisplayName().isEmpty()) {
538  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
539  }
540  if (caseDir.isEmpty()) {
541  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
542  }
543  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
544  }
545 
559  @Messages({
560  "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
561  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
562  })
563  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
565  try {
566  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
567  } catch (CaseMetadataException ex) {
568  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex);
569  }
571  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
572  }
573  openAsCurrentCase(new Case(metadata), false);
574  }
575 
581  public static boolean isCaseOpen() {
582  return currentCase != null;
583  }
584 
592  public static Case getCurrentCase() {
593  /*
594  * Throwing an unchecked exception is a bad idea here.
595  *
596  * TODO (JIRA-2229): Case.getCurrentCase() method throws unchecked
597  * IllegalStateException; change to throw checked exception or return
598  * null
599  */
600  if (null != currentCase) {
601  return currentCase;
602  } else {
603  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
604  }
605  }
606 
615  @Messages({
616  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
617  "Case.progressIndicatorTitle.closingCase=Closing Case"
618  })
619  public static void closeCurrentCase() throws CaseActionException {
620  synchronized (caseActionSerializationLock) {
621  if (null == currentCase) {
622  return;
623  }
624  Case closedCase = currentCase;
625  try {
626  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
627  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
628  currentCase = null;
629  closedCase.close();
630  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
631  } catch (CaseActionException ex) {
632  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
633  throw ex;
634  } finally {
637  }
638  }
639  }
640  }
641 
650  public static void deleteCurrentCase() throws CaseActionException {
651  synchronized (caseActionSerializationLock) {
652  if (null == currentCase) {
653  return;
654  }
655  CaseMetadata metadata = currentCase.getMetadata();
657  deleteCase(metadata);
658  }
659  }
660 
672  @Messages({
673  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
674  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
675  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
676  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
677  })
678  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
679  synchronized (caseActionSerializationLock) {
680  if (null != currentCase) {
681  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
682  }
683  }
684 
685  /*
686  * Set up either a GUI progress indicator without a cancel button (can't
687  * cancel deleting a case) or a logging progress indicator.
688  */
689  ProgressIndicator progressIndicator;
691  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
692  } else {
693  progressIndicator = new LoggingProgressIndicator();
694  }
695  progressIndicator.start(Bundle.Case_progressMessage_preparing());
696  try {
697  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
698  deleteCase(metadata, progressIndicator);
699  } else {
700  /*
701  * First, acquire an exclusive case directory lock. The case
702  * cannot be deleted if another node has it open.
703  */
704  progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
706  assert (null != dirLock);
707  deleteCase(metadata, progressIndicator);
708  } catch (CoordinationServiceException ex) {
709  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
710  }
711  }
712  } finally {
713  progressIndicator.finish();
714  }
715  }
716 
727  @Messages({
728  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
729  })
730  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
731  synchronized (caseActionSerializationLock) {
732  if (null != currentCase) {
733  try {
735  } catch (CaseActionException ex) {
736  /*
737  * Notify the user and continue (the error has already been
738  * logged in closeCurrentCase.
739  */
740  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
741  }
742  }
743  try {
744  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
745  newCurrentCase.open(isNewCase);
746  currentCase = newCurrentCase;
747  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
749  updateGUIForCaseOpened(newCurrentCase);
750  }
751  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
752  } catch (CaseActionCancelledException ex) {
753  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
754  throw ex;
755  } catch (CaseActionException ex) {
756  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
757  throw ex;
758  }
759  }
760  }
761 
770  private static String displayNameToUniqueName(String caseDisplayName) {
771  /*
772  * Replace all non-ASCII characters.
773  */
774  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
775 
776  /*
777  * Replace all control characters.
778  */
779  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
780 
781  /*
782  * Replace /, \, :, ?, space, ' ".
783  */
784  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
785 
786  /*
787  * Make it all lowercase.
788  */
789  uniqueCaseName = uniqueCaseName.toLowerCase();
790 
791  /*
792  * Add a time stamp for uniqueness.
793  */
794  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
795  Date date = new Date();
796  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
797 
798  return uniqueCaseName;
799  }
800 
809  static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
810 
811  File caseDirF = new File(caseDir);
812 
813  if (caseDirF.exists()) {
814  if (caseDirF.isFile()) {
815  throw new CaseActionException(
816  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
817 
818  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
819  throw new CaseActionException(
820  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
821  }
822  }
823 
824  try {
825  boolean result = (caseDirF).mkdirs(); // create root case Directory
826 
827  if (result == false) {
828  throw new CaseActionException(
829  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
830  }
831 
832  // create the folders inside the case directory
833  String hostClause = "";
834 
835  if (caseType == CaseType.MULTI_USER_CASE) {
836  hostClause = File.separator + NetworkUtils.getLocalHostName();
837  }
838  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
839  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
840  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
841  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
842 
843  if (result == false) {
844  throw new CaseActionException(
845  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
846  }
847 
848  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
849  result = new File(modulesOutDir).mkdir();
850 
851  if (result == false) {
852  throw new CaseActionException(
853  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
854  modulesOutDir));
855  }
856 
857  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
858  result = new File(reportsOutDir).mkdir();
859 
860  if (result == false) {
861  throw new CaseActionException(
862  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
863  modulesOutDir));
864 
865  }
866 
867  } catch (MissingResourceException | CaseActionException e) {
868  throw new CaseActionException(
869  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
870  }
871  }
872 
880  static Map<Long, String> getImagePaths(SleuthkitCase db) {
881  Map<Long, String> imgPaths = new HashMap<>();
882  try {
883  Map<Long, List<String>> imgPathsList = db.getImagePaths();
884  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
885  if (entry.getValue().size() > 0) {
886  imgPaths.put(entry.getKey(), entry.getValue().get(0));
887  }
888  }
889  } catch (TskCoreException ex) {
890  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
891  }
892  return imgPaths;
893  }
894 
911  @Messages({
912  "Case.progressMessage.deletingTextIndex=Deleting text index...",
913  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
914  "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
915  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
916  })
917  private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
918  boolean errorsOccurred = false;
919  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
920  /*
921  * Delete the case database from the database server.
922  */
923  try {
924  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
925  CaseDbConnectionInfo db;
927  Class.forName("org.postgresql.Driver"); //NON-NLS
928  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
929  Statement statement = connection.createStatement();) {
930  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
931  statement.execute(deleteCommand);
932  }
933  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
934  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);
935  errorsOccurred = true;
936  }
937  }
938 
939  /*
940  * Delete the text index.
941  */
942  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
943  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
944  try {
945  searchService.deleteTextIndex(metadata);
946  } catch (KeywordSearchServiceException ex) {
947  logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
948  errorsOccurred = true;
949  }
950  }
951 
952  /*
953  * Delete the case directory.
954  */
955  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
956  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
957  logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
958  errorsOccurred = true;
959  }
960 
961  /*
962  * If running in a GUI, remove the case from the Recent Cases menu
963  */
965  SwingUtilities.invokeLater(() -> {
966  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
967  });
968  }
969 
970  if (errorsOccurred) {
971  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
972  }
973  }
974 
985  @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
986  private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
987  try {
988  String resourcesNodeName = caseDir + "_resources";
989  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
990  if (null == lock) {
991  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
992  }
993  return lock;
994  } catch (InterruptedException ex) {
995  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
996  } catch (CoordinationServiceException ex) {
997  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
998  }
999  }
1000 
1001  private static String getNameForTitle(){
1002  //Method should become unnecessary once technical debt story 3334 is done.
1003  if (UserPreferences.getAppName().equals(Version.getName())){
1004  //Available version number is version number for this application
1005  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1006  }
1007  else {
1008  return UserPreferences.getAppName();
1009  }
1010  }
1011 
1015  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1017  SwingUtilities.invokeLater(() -> {
1018  /*
1019  * If the case database was upgraded for a new schema and a
1020  * backup database was created, notify the user.
1021  */
1022  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1023  String backupDbPath = caseDb.getBackupDatabasePath();
1024  if (null != backupDbPath) {
1025  JOptionPane.showMessageDialog(
1026  mainFrame,
1027  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1028  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1029  JOptionPane.INFORMATION_MESSAGE);
1030  }
1031 
1032  /*
1033  * Look for the files for the data sources listed in the case
1034  * database and give the user the opportunity to locate any that
1035  * are missing.
1036  */
1037  Map<Long, String> imgPaths = getImagePaths(caseDb);
1038  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1039  long obj_id = entry.getKey();
1040  String path = entry.getValue();
1041  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1042  if (!fileExists) {
1043  int response = JOptionPane.showConfirmDialog(
1044  mainFrame,
1045  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1046  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1047  JOptionPane.YES_NO_OPTION);
1048  if (response == JOptionPane.YES_OPTION) {
1049  MissingImageDialog.makeDialog(obj_id, caseDb);
1050  } else {
1051  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1052 
1053  }
1054  }
1055  }
1056 
1057  /*
1058  * Enable the case-specific actions.
1059  */
1060  CallableSystemAction.get(AddImageAction.class).setEnabled(true);
1061  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1062  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
1063  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
1064  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1065  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1066  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1067 
1068  /*
1069  * Add the case to the recent cases tracker that supplies a list
1070  * of recent cases to the recent cases menu item and the
1071  * open/create case dialog.
1072  */
1073  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1074 
1075  /*
1076  * Open the top components (windows within the main application
1077  * window).
1078  */
1079  if (newCurrentCase.hasData()) {
1081  }
1082 
1083  /*
1084  * Reset the main window title to:
1085  *
1086  * [curent case display name] - [application name].
1087  */
1088  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1089  });
1090  }
1091  }
1092 
1093  /*
1094  * Update the GUI to to reflect the lack of a current case.
1095  */
1096  private static void updateGUIForCaseClosed() {
1098  SwingUtilities.invokeLater(() -> {
1099  /*
1100  * Close the top components (windows within the main application
1101  * window).
1102  */
1104 
1105  /*
1106  * Disable the case-specific menu items.
1107  */
1108  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1109  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1110  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false);
1111  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1112  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1113  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1114  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1115 
1116  /*
1117  * Clear the notifications in the notfier component in the lower
1118  * right hand corner of the main application window.
1119  */
1121 
1122  /*
1123  * Reset the main window title to be just the application name,
1124  * instead of [curent case display name] - [application name].
1125  */
1126  mainFrame.setTitle(getNameForTitle());
1127  });
1128  }
1129  }
1130 
1134  private static void clearTempSubDir(String tempSubDirPath) {
1135  File tempFolder = new File(tempSubDirPath);
1136  if (tempFolder.isDirectory()) {
1137  File[] files = tempFolder.listFiles();
1138  if (files.length > 0) {
1139  for (File file : files) {
1140  if (file.isDirectory()) {
1141  FileUtil.deleteDir(file);
1142  } else {
1143  file.delete();
1144  }
1145  }
1146  }
1147  }
1148  }
1149 
1155  public SleuthkitCase getSleuthkitCase() {
1156  return this.caseDb;
1157  }
1158 
1165  return caseServices;
1166  }
1167 
1174  return metadata.getCaseType();
1175  }
1176 
1182  public String getCreatedDate() {
1183  return metadata.getCreatedDate();
1184  }
1185 
1191  public String getName() {
1192  return metadata.getCaseName();
1193  }
1194 
1200  public String getDisplayName() {
1201  return metadata.getCaseDisplayName();
1202  }
1203 
1209  public String getNumber() {
1210  return metadata.getCaseNumber();
1211  }
1212 
1218  public String getExaminer() {
1219  return metadata.getExaminer();
1220  }
1221 
1227  public String getExaminerPhone() {
1228  return metadata.getExaminerPhone();
1229  }
1230 
1236  public String getExaminerEmail() {
1237  return metadata.getExaminerEmail();
1238  }
1239 
1245  public String getCaseNotes() {
1246  return metadata.getCaseNotes();
1247  }
1248 
1254  public String getCaseDirectory() {
1255  return metadata.getCaseDirectory();
1256  }
1257 
1266  public String getOutputDirectory() {
1267  String caseDirectory = getCaseDirectory();
1268  Path hostPath;
1269  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1270  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1271  } else {
1272  hostPath = Paths.get(caseDirectory);
1273  }
1274  if (!hostPath.toFile().exists()) {
1275  hostPath.toFile().mkdirs();
1276  }
1277  return hostPath.toString();
1278  }
1279 
1286  public String getTempDirectory() {
1287  return getOrCreateSubdirectory(TEMP_FOLDER);
1288  }
1289 
1296  public String getCacheDirectory() {
1297  return getOrCreateSubdirectory(CACHE_FOLDER);
1298  }
1299 
1306  public String getExportDirectory() {
1307  return getOrCreateSubdirectory(EXPORT_FOLDER);
1308  }
1309 
1316  public String getLogDirectoryPath() {
1317  return getOrCreateSubdirectory(LOG_FOLDER);
1318  }
1319 
1326  public String getReportDirectory() {
1327  return getOrCreateSubdirectory(REPORTS_FOLDER);
1328  }
1329 
1336  public String getModuleDirectory() {
1337  return getOrCreateSubdirectory(MODULE_FOLDER);
1338  }
1339 
1348  Path path = Paths.get(getModuleDirectory());
1350  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1351  } else {
1352  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1353  }
1354  }
1355 
1365  public List<Content> getDataSources() throws TskCoreException {
1366  List<Content> list = caseDb.getRootObjects();
1367  hasDataSources = (list.size() > 0);
1368  return list;
1369  }
1370 
1376  public Set<TimeZone> getTimeZones() {
1377  Set<TimeZone> timezones = new HashSet<>();
1378  try {
1379  for (Content c : getDataSources()) {
1380  final Content dataSource = c.getDataSource();
1381  if ((dataSource != null) && (dataSource instanceof Image)) {
1382  Image image = (Image) dataSource;
1383  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1384  }
1385  }
1386  } catch (TskCoreException ex) {
1387  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1388  }
1389  return timezones;
1390  }
1391 
1398  public String getTextIndexName() {
1399  return getMetadata().getTextIndexName();
1400  }
1401 
1408  public boolean hasData() {
1409  if (!hasDataSources) {
1410  try {
1411  hasDataSources = (getDataSources().size() > 0);
1412  } catch (TskCoreException ex) {
1413  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1414  }
1415  }
1416  return hasDataSources;
1417  }
1418 
1429  public void notifyAddingDataSource(UUID eventId) {
1430  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1431  }
1432 
1443  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1444  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1445  }
1446 
1458  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1459  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1460  }
1461 
1469  public void notifyContentTagAdded(ContentTag newTag) {
1470  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1471  }
1472 
1480  public void notifyContentTagDeleted(ContentTag deletedTag) {
1481  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1482  }
1483 
1491  public void notifyTagDefinitionChanged(String changedTagName) {
1492  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1493  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1494  }
1495 
1503  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1504  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1505  }
1506 
1514  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1515  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1516  }
1517 
1529  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1530  String normalizedLocalPath;
1531  try {
1532  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1533  } catch (InvalidPathException ex) {
1534  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1535  throw new TskCoreException(errorMsg, ex);
1536  }
1537  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName);
1538  eventPublisher.publish(new ReportAddedEvent(report));
1539  }
1540 
1549  public List<Report> getAllReports() throws TskCoreException {
1550  return this.caseDb.getAllReports();
1551  }
1552 
1561  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1562  for (Report report : reports) {
1563  this.caseDb.deleteReport(report);
1564  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1565  }
1566  }
1567 
1573  CaseMetadata getMetadata() {
1574  return metadata;
1575  }
1576 
1584  @Messages({
1585  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1586  })
1587  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1588  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1589  try {
1590  metadata.setCaseDetails(caseDetails);
1591  } catch (CaseMetadataException ex) {
1592  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1593  }
1594  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1595  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1596  }
1597  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1598  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1599  }
1600  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1601  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1602  }
1603  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1604  if (RuntimeProperties.runningWithGUI()) {
1605  SwingUtilities.invokeLater(() -> {
1606  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1607  try {
1608  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1609  } catch (Exception ex) {
1610  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1611  }
1612  });
1613  }
1614  }
1615 
1627  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1628  metadata = new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails);
1629  }
1630 
1636  private Case(CaseMetadata caseMetaData) {
1637  metadata = caseMetaData;
1638  }
1639 
1655  @Messages({
1656  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1657  "Case.progressIndicatorTitle.openingCase=Opening Case",
1658  "Case.progressIndicatorCancelButton.label=Cancel",
1659  "Case.progressMessage.preparing=Preparing...",
1660  "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1661  "Case.progressMessage.cancelling=Cancelling...",
1662  "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1663  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1664  })
1665  private void open(boolean isNewCase) throws CaseActionException {
1666  /*
1667  * Create and start either a GUI progress indicator with a Cancel button
1668  * or a logging progress indicator.
1669  */
1670  CancelButtonListener cancelButtonListener = null;
1671  ProgressIndicator progressIndicator;
1673  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1674  String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1675  progressIndicator = new ModalDialogProgressIndicator(
1676  mainFrame,
1677  progressIndicatorTitle,
1678  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1679  Bundle.Case_progressIndicatorCancelButton_label(),
1680  cancelButtonListener);
1681  } else {
1682  progressIndicator = new LoggingProgressIndicator();
1683  }
1684  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1685 
1686  /*
1687  * Creating/opening a case is always done by creating a task running in
1688  * the same non-UI thread that will be used to close the case, so a
1689  * single-threaded executor service is created here and saved as case
1690  * state (must be volatile for cancellation to work).
1691  *
1692  * --- If the case is a single-user case, this supports cancelling
1693  * opening of the case by cancelling the task.
1694  *
1695  * --- If the case is a multi-user case, this still supports
1696  * cancellation, but it also makes it possible for the shared case
1697  * directory lock held as long as the case is open to be released in the
1698  * same thread in which it was acquired, as is required by the
1699  * coordination service.
1700  */
1701  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1702  caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1703  Future<Void> future = caseLockingExecutor.submit(() -> {
1704  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1705  open(isNewCase, progressIndicator);
1706  } else {
1707  /*
1708  * First, acquire a shared case directory lock that will be held
1709  * as long as this node has this case open. This will prevent
1710  * deletion of the case by another node. Next, acquire an
1711  * exclusive case resources lock to ensure only one node at a
1712  * time can create/open/upgrade/close the case resources.
1713  */
1714  progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1717  assert (null != resourcesLock);
1718  open(isNewCase, progressIndicator);
1719  } catch (CaseActionException ex) {
1720  releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
1721  throw ex;
1722  }
1723  }
1724  return null;
1725  });
1726  if (null != cancelButtonListener) {
1727  cancelButtonListener.setCaseActionFuture(future);
1728  }
1729 
1730  /*
1731  * Wait for the case creation/opening task to finish.
1732  */
1733  try {
1734  future.get();
1735  } catch (InterruptedException discarded) {
1736  /*
1737  * The thread this method is running in has been interrupted. Cancel
1738  * the create/open task, wait for it to finish, and shut down the
1739  * executor. This can be done safely because if the task is
1740  * completed with a cancellation condition, the case will have been
1741  * closed and the case directory lock released will have been
1742  * released.
1743  */
1744  if (null != cancelButtonListener) {
1745  cancelButtonListener.actionPerformed(null);
1746  } else {
1747  future.cancel(true);
1748  }
1749  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1750  } catch (CancellationException discarded) {
1751  /*
1752  * The create/open task has been cancelled. Wait for it to finish,
1753  * and shut down the executor. This can be done safely because if
1754  * the task is completed with a cancellation condition, the case
1755  * will have been closed and the case directory lock released will
1756  * have been released.
1757  */
1758  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1759  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1760  } catch (ExecutionException ex) {
1761  /*
1762  * The create/open task has thrown an exception. Wait for it to
1763  * finish, and shut down the executor. This can be done safely
1764  * because if the task is completed with an execution condition, the
1765  * case will have been closed and the case directory lock released
1766  * will have been released.
1767  */
1768  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1769  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1770  } finally {
1771  progressIndicator.finish();
1772  }
1773  }
1774 
1786  private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
1787  try {
1788  if (Thread.currentThread().isInterrupted()) {
1789  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1790  }
1791 
1792  if (isNewCase) {
1793  createCaseData(progressIndicator);
1794  } else {
1795  openCaseData(progressIndicator);
1796  }
1797 
1798  if (Thread.currentThread().isInterrupted()) {
1799  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1800  }
1801 
1802  openServices(progressIndicator);
1803 
1804  if (Thread.currentThread().isInterrupted()) {
1805  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1806  }
1807  } catch (CaseActionException ex) {
1808  /*
1809  * Cancellation or failure. Clean up. The sleep is a little hack to
1810  * clear the interrupted flag for this thread if this is a
1811  * cancellation scenario, so that the clean up can run to completion
1812  * in this thread.
1813  */
1814  try {
1815  Thread.sleep(1);
1816  } catch (InterruptedException discarded) {
1817  }
1818  close(progressIndicator);
1819  throw ex;
1820  }
1821  }
1822 
1833  @Messages({
1834  "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1835  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1836  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}",
1837  "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
1838  })
1839  private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1840  /*
1841  * Create the case directory, if it does not already exist.
1842  *
1843  * TODO (JIRA-2180): Always create the case directory as part of the
1844  * case creation process.
1845  */
1846  if (new File(metadata.getCaseDirectory()).exists() == false) {
1847  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1848  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
1849  }
1850 
1851  /*
1852  * Create the case database.
1853  */
1854  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1855  try {
1856  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1857  /*
1858  * For single-user cases, the case database is a SQLite database
1859  * with a standard name, physically located in the root of the
1860  * case directory.
1861  */
1862  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
1863  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
1864  } else {
1865  /*
1866  * For multi-user cases, the case database is a PostgreSQL
1867  * database with a name derived from the case display name,
1868  * physically located on a database server.
1869  */
1870  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1871  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
1872  }
1873  } catch (TskCoreException ex) {
1874  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
1875  } catch (UserPreferencesException ex) {
1876  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1877  } catch (CaseMetadataException ex) {
1878  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1879  }
1880  }
1881 
1892  @Messages({
1893  "Case.progressMessage.openingCaseDatabase=Opening case database...",
1894  "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database.",
1895  "Case.unsupportedSchemaVersionMessage=Unsupported DB schema version - see log for details",
1896  "Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-User.",
1897  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. "
1898  + "See Tools, Options, Multi-user."
1899  })
1900  private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1901  try {
1902  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
1903  String databaseName = metadata.getCaseDatabaseName();
1904  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1905  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
1907  try {
1908  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1909  } catch (UserPreferencesException ex) {
1910  throw new CaseActionException(Case_databaseConnectionInfo_error_msg(), ex);
1911  }
1912  } else {
1913  throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled());
1914  }
1915  } catch (TskUnsupportedSchemaVersionException ex) {
1916  throw new CaseActionException(Bundle.Case_unsupportedSchemaVersionMessage(), ex);
1917  } catch (TskCoreException ex) {
1918  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
1919  }
1920  }
1921 
1930  @Messages({
1931  "Case.progressMessage.switchingLogDirectory=Switching log directory...",
1932  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
1933  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
1934  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
1935  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
1936  private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
1937  /*
1938  * Switch to writing to the application logs in the logs subdirectory of
1939  * the case directory.
1940  */
1941  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
1943  if (Thread.currentThread().isInterrupted()) {
1944  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1945  }
1946 
1947  /*
1948  * Clear the temp subdirectory of the case directory.
1949  */
1950  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
1952  if (Thread.currentThread().isInterrupted()) {
1953  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1954  }
1955 
1956  /*
1957  * Open the case-level services.
1958  */
1959  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
1960  this.caseServices = new Services(caseDb);
1961  if (Thread.currentThread().isInterrupted()) {
1962  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1963  }
1964 
1965  /*
1966  * Allow any registered application services to open any resources
1967  * specific to this case.
1968  */
1969  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
1971  if (Thread.currentThread().isInterrupted()) {
1972  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1973  }
1974 
1975  /*
1976  * If this case is a multi-user case, set up for communication with
1977  * other nodes.
1978  */
1979  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1980  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
1981  try {
1982  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
1983  if (Thread.currentThread().isInterrupted()) {
1984  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1985  }
1986  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
1987  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
1988  /*
1989  * The collaboration monitor and event channel are not
1990  * essential. Log an error and notify the user, but do not
1991  * throw.
1992  */
1993  logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
1995  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
1996  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"),
1997  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
1998  }
1999  }
2000  }
2001  }
2002 
2007  @NbBundle.Messages({
2008  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2009  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2010  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2011  })
2012  private void openAppServiceCaseResources() throws CaseActionException {
2013  /*
2014  * Each service gets its own independently cancellable/interruptible
2015  * task, running in a named thread managed by an executor service, with
2016  * its own progress indicator. This allows for cancellation of the
2017  * opening of case resources for individual services. It also makes it
2018  * possible to ensure that each service task completes before the next
2019  * one starts by awaiting termination of the executor service.
2020  */
2021  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2022  /*
2023  * Create a progress indicator for the task and start the task. If
2024  * running with a GUI, the progress indicator will be a dialog box
2025  * with a Cancel button.
2026  */
2027  CancelButtonListener cancelButtonListener = null;
2028  ProgressIndicator progressIndicator;
2030  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2031  progressIndicator = new ModalDialogProgressIndicator(
2032  mainFrame,
2033  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2034  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2035  Bundle.Case_progressIndicatorCancelButton_label(),
2036  cancelButtonListener);
2037  } else {
2038  progressIndicator = new LoggingProgressIndicator();
2039  }
2040  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2041  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2042  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2043  threadNameSuffix = threadNameSuffix.toLowerCase();
2044  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2045  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2046  Future<Void> future = executor.submit(() -> {
2047  service.openCaseResources(context);
2048  return null;
2049  });
2050  if (null != cancelButtonListener) {
2051  cancelButtonListener.setCaseContext(context);
2052  cancelButtonListener.setCaseActionFuture(future);
2053  }
2054 
2055  /*
2056  * Wait for the task to either be completed or
2057  * cancelled/interrupted, or for the opening of the case to be
2058  * cancelled.
2059  */
2060  try {
2061  future.get();
2062  } catch (InterruptedException discarded) {
2063  /*
2064  * The parent create/open case task has been cancelled.
2065  */
2066  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()));
2067  future.cancel(true);
2068  } catch (CancellationException discarded) {
2069  /*
2070  * The opening of case resources by the application service has
2071  * been cancelled, so the executor service has thrown. Note that
2072  * there is no guarantee the task itself has responded to the
2073  * cancellation request yet.
2074  */
2075  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()));
2076  } catch (ExecutionException ex) {
2077  /*
2078  * An exception was thrown while executing the task. The
2079  * case-specific application service resources are not
2080  * essential. Log an error and notify the user if running the
2081  * desktop GUI, but do not throw.
2082  */
2083  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2085  SwingUtilities.invokeLater(() -> {
2086  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2087  });
2088  }
2089  } finally {
2090  /*
2091  * Shut down the executor service and wait for it to finish.
2092  * This ensures that the task has finished. Without this, it
2093  * would be possible to start the next task before the current
2094  * task responded to a cancellation request.
2095  */
2097  progressIndicator.finish();
2098  }
2099 
2100  if (Thread.currentThread().isInterrupted()) {
2101  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2102  }
2103  }
2104  }
2105 
2109  private void close() throws CaseActionException {
2110  /*
2111  * Set up either a GUI progress indicator without a Cancel button or a
2112  * logging progress indicator.
2113  */
2114  ProgressIndicator progressIndicator;
2116  progressIndicator = new ModalDialogProgressIndicator(
2117  mainFrame,
2118  Bundle.Case_progressIndicatorTitle_closingCase());
2119  } else {
2120  progressIndicator = new LoggingProgressIndicator();
2121  }
2122  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2123 
2124  /*
2125  * Closing a case is always done in the same non-UI thread that
2126  * opened/created the case. If the case is a multi-user case, this
2127  * ensures that case directory lock that is held as long as the case is
2128  * open is released in the same thread in which it was acquired, as is
2129  * required by the coordination service.
2130  */
2131  Future<Void> future = caseLockingExecutor.submit(() -> {
2132  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2133  close(progressIndicator);
2134  } else {
2135  /*
2136  * Acquire an exclusive case resources lock to ensure only one
2137  * node at a time can create/open/upgrade/close the case
2138  * resources.
2139  */
2140  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2142  assert (null != resourcesLock);
2143  close(progressIndicator);
2144  } finally {
2145  /*
2146  * Always release the case directory lock that was acquired
2147  * when the case was opened.
2148  */
2150  }
2151  }
2152  return null;
2153  });
2154 
2155  try {
2156  future.get();
2157  } catch (InterruptedException | CancellationException unused) {
2158  /*
2159  * The wait has been interrupted by interrupting the thread running
2160  * this method. Not allowing cancellation of case closing, so ignore
2161  * the interrupt. Likewsie, cancellation of the case closing task is
2162  * not supported.
2163  */
2164  } catch (ExecutionException ex) {
2165  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2166  } finally {
2167  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
2168  progressIndicator.finish();
2169  }
2170  }
2171 
2177  @Messages({
2178  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2179  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2180  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2181  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2182  })
2183  private void close(ProgressIndicator progressIndicator) {
2185 
2186  /*
2187  * Stop sending/receiving case events to and from other nodes if this is
2188  * a multi-user case.
2189  */
2190  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2191  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2192  if (null != collaborationMonitor) {
2193  collaborationMonitor.shutdown();
2194  }
2195  eventPublisher.closeRemoteEventChannel();
2196  }
2197 
2198  /*
2199  * Allow all registered application services providers to close
2200  * resources related to the case.
2201  */
2202  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2204 
2205  /*
2206  * Close the case-level services.
2207  */
2208  if (null != caseServices) {
2209  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2210  try {
2211  this.caseServices.close();
2212  } catch (IOException ex) {
2213  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2214  }
2215  }
2216 
2217  /*
2218  * Close the case database
2219  */
2220  if (null != caseDb) {
2221  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2222  caseDb.close();
2223  }
2224 
2225  /*
2226  * Switch the log directory.
2227  */
2228  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2230  }
2231 
2236  @Messages({
2237  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2238  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2239  })
2241  /*
2242  * Each service gets its own independently cancellable task, and thus
2243  * its own task progress indicator.
2244  */
2245  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2246  ProgressIndicator progressIndicator;
2248  progressIndicator = new ModalDialogProgressIndicator(
2249  mainFrame,
2250  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2251  } else {
2252  progressIndicator = new LoggingProgressIndicator();
2253  }
2254  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2255  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2256  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2257  threadNameSuffix = threadNameSuffix.toLowerCase();
2258  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2259  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2260  Future<Void> future = executor.submit(() -> {
2261  service.closeCaseResources(context);
2262  return null;
2263  });
2264  try {
2265  future.get();
2266  } catch (InterruptedException ex) {
2267  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2268  } catch (CancellationException ex) {
2269  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2270  } catch (ExecutionException ex) {
2271  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2273  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2274  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2275  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2276  }
2277  } finally {
2279  progressIndicator.finish();
2280  }
2281  }
2282  }
2283 
2292  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2293  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2294  try {
2295  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2296  if (null == caseDirLock) {
2297  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2298  }
2299  } catch (InterruptedException | CoordinationServiceException ex) {
2300  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2301  }
2302  }
2303 
2309  private void releaseSharedCaseDirLock(String caseDir) {
2310  if (caseDirLock != null) {
2311  try {
2312  caseDirLock.release();
2313  caseDirLock = null;
2315  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2316  }
2317  }
2318  }
2319 
2326  private String getOrCreateSubdirectory(String subDirectoryName) {
2327  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2328  if (!subDirectory.exists()) {
2329  subDirectory.mkdirs();
2330  }
2331  return subDirectory.toString();
2332 
2333  }
2334 
2339  @ThreadSafe
2340  private final static class CancelButtonListener implements ActionListener {
2341 
2342  private final String cancellationMessage;
2343  @GuardedBy("this")
2344  private boolean cancelRequested;
2345  @GuardedBy("this")
2347  @GuardedBy("this")
2348  private Future<?> caseActionFuture;
2349 
2358  private CancelButtonListener(String cancellationMessage) {
2359  this.cancellationMessage = cancellationMessage;
2360  }
2361 
2367  private synchronized void setCaseContext(CaseContext caseContext) {
2368  this.caseContext = caseContext;
2369  /*
2370  * If the cancel button has already been pressed, pass the
2371  * cancellation on to the case context.
2372  */
2373  if (cancelRequested) {
2374  cancel();
2375  }
2376  }
2377 
2383  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
2384  this.caseActionFuture = caseActionFuture;
2385  /*
2386  * If the cancel button has already been pressed, cancel the Future
2387  * of the task.
2388  */
2389  if (cancelRequested) {
2390  cancel();
2391  }
2392  }
2393 
2399  @Override
2400  public synchronized void actionPerformed(ActionEvent event) {
2401  cancel();
2402  }
2403 
2407  private void cancel() {
2408  /*
2409  * At a minimum, set the cancellation requested flag of this
2410  * listener.
2411  */
2412  this.cancelRequested = true;
2413  if (null != this.caseContext) {
2414  /*
2415  * Set the cancellation request flag and display the
2416  * cancellation message in the progress indicator for the case
2417  * context associated with this listener.
2418  */
2420  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
2421  if (progressIndicator instanceof ModalDialogProgressIndicator) {
2422  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2423  }
2424  }
2425  this.caseContext.requestCancel();
2426  }
2427  if (null != this.caseActionFuture) {
2428  /*
2429  * Cancel the Future of the task associated with this listener.
2430  * Note that the task thread will be interrupted if the task is
2431  * blocked.
2432  */
2433  this.caseActionFuture.cancel(true);
2434  }
2435  }
2436  }
2437 
2441  private static class TaskThreadFactory implements ThreadFactory {
2442 
2443  private final String threadName;
2444 
2445  private TaskThreadFactory(String threadName) {
2446  this.threadName = threadName;
2447  }
2448 
2449  @Override
2450  public Thread newThread(Runnable task) {
2451  return new Thread(task, threadName);
2452  }
2453 
2454  }
2455 
2463  @Deprecated
2464  public static String getAppName() {
2465  return UserPreferences.getAppName();
2466  }
2467 
2487  @Deprecated
2488  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2489  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2490  }
2491 
2512  @Deprecated
2513  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2514  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2515  }
2516 
2528  @Deprecated
2529  public static void open(String caseMetadataFilePath) throws CaseActionException {
2530  openAsCurrentCase(caseMetadataFilePath);
2531  }
2532 
2542  @Deprecated
2543  public void closeCase() throws CaseActionException {
2544  closeCurrentCase();
2545  }
2546 
2552  @Deprecated
2553  public static void invokeStartupDialog() {
2555  }
2556 
2570  @Deprecated
2571  public static String convertTimeZone(String timeZoneId) {
2572  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2573  }
2574 
2584  @Deprecated
2585  public static boolean pathExists(String filePath) {
2586  return new File(filePath).isFile();
2587  }
2588 
2597  @Deprecated
2598  public static String getAutopsyVersion() {
2599  return Version.getVersion();
2600  }
2601 
2609  @Deprecated
2610  public static boolean existsCurrentCase() {
2611  return isCaseOpen();
2612  }
2613 
2623  @Deprecated
2624  public static String getModulesOutputDirRelPath() {
2625  return "ModuleOutput"; //NON-NLS
2626  }
2627 
2637  @Deprecated
2638  public static PropertyChangeSupport
2640  return new PropertyChangeSupport(Case.class
2641  );
2642  }
2643 
2652  @Deprecated
2653  public String getModulesOutputDirAbsPath() {
2654  return getModuleDirectory();
2655  }
2656 
2671  @Deprecated
2672  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
2673  try {
2674  Image newDataSource = caseDb.getImageById(imgId);
2675  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
2676  return newDataSource;
2677  } catch (TskCoreException ex) {
2678  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
2679  }
2680  }
2681 
2689  @Deprecated
2690  public Set<TimeZone> getTimeZone() {
2691  return getTimeZones();
2692  }
2693 
2704  @Deprecated
2705  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
2706  deleteReports(reports);
2707  }
2708 
2709 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:136
List< Content > getDataSources()
Definition: Case.java:1365
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1480
Case(CaseMetadata caseMetaData)
Definition: Case.java:1636
static CaseType fromString(String typeName)
Definition: Case.java:179
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:133
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:536
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1514
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:2672
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:986
static boolean existsCurrentCase()
Definition: Case.java:2610
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:394
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:135
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:124
static final String EXPORT_FOLDER
Definition: Case.java:128
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1491
static volatile Frame mainFrame
Definition: Case.java:138
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2571
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:127
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1627
void createCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1839
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1529
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1015
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2513
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:125
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:678
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:2705
volatile ExecutorService caseLockingExecutor
Definition: Case.java:141
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:2367
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1134
static boolean isValidName(String caseName)
Definition: Case.java:478
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2293
CollaborationMonitor collaborationMonitor
Definition: Case.java:144
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2309
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:2624
void openCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1900
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:2400
static final String MODULE_FOLDER
Definition: Case.java:132
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2488
synchronized void openRemoteEventChannel(String channelName)
void openServices(ProgressIndicator progressIndicator)
Definition: Case.java:1936
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:770
void open(boolean isNewCase)
Definition: Case.java:1665
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:563
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:454
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:123
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2183
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1503
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2639
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:382
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2383
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:444
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1561
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1458
static boolean pathExists(String filePath)
Definition: Case.java:2585
static void open(String caseMetadataFilePath)
Definition: Case.java:2529
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:730
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2326
boolean equalsName(String otherTypeName)
Definition: Case.java:237
static final String EVENT_CHANNEL_NAME
Definition: Case.java:126
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:419
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:129
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:139
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1429
CoordinationService.Lock caseDirLock
Definition: Case.java:142
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:434
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1469
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:134
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:509
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1443
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:464
static final Object caseActionSerializationLock
Definition: Case.java:137
void open(boolean isNewCase, ProgressIndicator progressIndicator)
Definition: Case.java:1786
static final String REPORTS_FOLDER
Definition: Case.java:130
static final String TEMP_FOLDER
Definition: Case.java:131
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:409
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:917

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