Autopsy  4.1
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.lang.reflect.InvocationTargetException;
29 import java.nio.file.InvalidPathException;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.sql.Connection;
33 import java.sql.DriverManager;
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.TimeUnit;
52 import java.util.logging.Level;
53 import java.util.stream.Collectors;
54 import java.util.stream.Stream;
55 import javax.annotation.concurrent.GuardedBy;
56 import javax.swing.JOptionPane;
57 import javax.swing.SwingUtilities;
58 import org.openide.util.Lookup;
59 import org.openide.util.NbBundle;
60 import org.openide.util.NbBundle.Messages;
61 import org.openide.util.actions.CallableSystemAction;
62 import org.openide.windows.WindowManager;
110 
114 public class Case {
115 
116  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
117  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
118  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
119  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
120  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
121  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
122  private static final String LOG_FOLDER = "Log"; //NON-NLS
123  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
124  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
125  private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60;
126  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
127  private static final Logger logger = Logger.getLogger(Case.class.getName());
129 
130  /*
131  * The following fields are the mutable state associated with the current
132  * case concept. The currentCase field is guarded for writes by the
133  * currentCaseWriteLock. The field is also volatile to allow non-locking
134  * reads via the isCaseOpen and getCurrentCase methods. This is unfortunate,
135  * but Case clients that do not respond correctly to CURRENT_CASE closed
136  * events may call these methods and that would be a source of potential
137  * deadlock if the currentCaseWriteLock was used to guard read access.
138  *
139  * TODO (JIRA-2228): Throw CaseClosedException from Case instance methods.
140  */
141  private static final Object currentCaseWriteLock = new Object();
142  @GuardedBy("currentCaseWriteLock")
143  private static volatile Case currentCase;
144 
145  /*
146  * The application name, used to make the title of the main application
147  * window [application name] when there is no open case and [curent case
148  * display name] - [application name] when there is an open case.
149  * Initialized by getting the main window title before a case has been
150  * opened. A reference to the main window frame is obtained at the same time
151  * as a convenmient side effect for parenting dialogs.
152  *
153  * TODO (JIRA-2231): Make the application name a RuntimeProperties item.
154  */
155  @GuardedBy("currentCaseWriteLock")
156  private static Frame mainFrame;
157  @GuardedBy("currentCaseWriteLock")
158  private static String appName;
159 
160  /*
161  * Case instance data.
162  */
165  private ExecutorService caseLockingExecutor;
167  private SleuthkitErrorReporter sleuthkitErrorReporter;
168  private CollaborationMonitor collaborationMonitor;
170  private boolean hasDataSources;
171 
175  public enum CaseType {
176 
177  SINGLE_USER_CASE("Single-user case"), //NON-NLS
178  MULTI_USER_CASE("Multi-user case"); //NON-NLS
179 
180  private final String typeName;
181 
189  public static CaseType fromString(String typeName) {
190  if (typeName != null) {
191  for (CaseType c : CaseType.values()) {
192  if (typeName.equalsIgnoreCase(c.toString())) {
193  return c;
194  }
195  }
196  }
197  return null;
198  }
199 
205  @Override
206  public String toString() {
207  return typeName;
208  }
209 
215  @Messages({
216  "Case_caseType_singleUser=Single-user case",
217  "Case_caseType_multiUser=Multi-user case"
218  })
220  if (fromString(typeName) == SINGLE_USER_CASE) {
221  return Bundle.Case_caseType_singleUser();
222  } else {
223  return Bundle.Case_caseType_multiUser();
224  }
225  }
226 
232  private CaseType(String typeName) {
233  this.typeName = typeName;
234  }
235 
246  @Deprecated
247  public boolean equalsName(String otherTypeName) {
248  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
249  }
250 
251  };
252 
257  public enum Events {
258 
364  };
365 
372  public static void addPropertyChangeListener(PropertyChangeListener listener) {
373  addEventSubscriber(Stream.of(Events.values())
374  .map(Events::toString)
375  .collect(Collectors.toSet()), listener);
376  }
377 
384  public static void removePropertyChangeListener(PropertyChangeListener listener) {
385  removeEventSubscriber(Stream.of(Events.values())
386  .map(Events::toString)
387  .collect(Collectors.toSet()), listener);
388  }
389 
396  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
397  eventPublisher.addSubscriber(eventNames, subscriber);
398  }
399 
406  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
407  eventPublisher.addSubscriber(eventName, subscriber);
408  }
409 
416  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
417  eventPublisher.removeSubscriber(eventName, subscriber);
418  }
419 
426  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
427  eventPublisher.removeSubscriber(eventNames, subscriber);
428  }
429 
438  public static boolean isValidName(String caseName) {
439  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
440  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
441  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
442  }
443 
466  @Messages({
467  "# {0} - exception message", "Case.exceptionMessage.wrapperMessage={0}"
468  })
469  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
470  synchronized (currentCaseWriteLock) {
473  }
474 
475  if (null != currentCase) {
476  String previousCaseDisplayName = currentCase.getDisplayName();
477  String previousCaseName = currentCase.getName();
478  String previousCaseDir = currentCase.getCaseDirectory();
479  try {
481  } catch (CaseActionException ex) {
482  logger.log(Level.SEVERE, String.format("Error closing the previous current case %s (%s) in %s", previousCaseDisplayName, previousCaseName, previousCaseDir), ex); //NON-NLS
483  }
484  }
485 
486  logger.log(Level.INFO, "Creating current case {0} in {1}", new Object[]{caseDisplayName, caseDir}); //NON-NLS
487  Case newCurrentCase = new Case();
488  newCurrentCase.create(caseType, caseDir, caseDisplayName, caseNumber, examiner);
489  currentCase = newCurrentCase;
490  logger.log(Level.INFO, "Created currrent case {0} ({1}) in {2}", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
492  updateGUIForCaseOpened(newCurrentCase);
493  }
494  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, newCurrentCase));
495  }
496  }
497 
511  @Messages({
512  "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}",
513  "Case.progressIndicatorTitle.openingCase=Opening Case",
514  "Case.exceptionMessage.failedToReadMetadata=Failed to read metadata."
515  })
516  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
517  synchronized (currentCaseWriteLock) {
520  }
521 
522  if (null != currentCase) {
523  try {
525  } catch (CaseActionException ex) {
526  logger.log(Level.SEVERE, "Error closing the previous current case", ex);
527  }
528  }
529 
530  Case newCurrentCase = new Case();
531  logger.log(Level.INFO, "Opening case with metadata file path {0} as current case", caseMetadataFilePath); //NON-NLS
532  newCurrentCase.open(Paths.get(caseMetadataFilePath));
533  currentCase = newCurrentCase;
534  logger.log(Level.INFO, "Opened case with metadata file path {0} as current case", caseMetadataFilePath); //NON-NLS
536  updateGUIForCaseOpened(newCurrentCase);
537  }
538  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
539  }
540  }
541 
547  public static boolean isCaseOpen() {
548  return currentCase != null;
549  }
550 
558  public static Case getCurrentCase() {
559  Case caseToReturn = currentCase;
560  if (null != caseToReturn) {
561  return caseToReturn;
562  } else {
563  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
564  }
565  }
566 
575  @Messages({
576  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
577  "Case.progressIndicatorTitle.closingCase=Closing Case"
578  })
579  public static void closeCurrentCase() throws CaseActionException {
580  synchronized (currentCaseWriteLock) {
581  if (null == currentCase) {
582  return;
583  }
584  String caseName = currentCase.getName();
585  String caseDir = currentCase.getCaseDirectory();
586  try {
587  Case closedCase = currentCase;
588  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
589  logger.log(Level.INFO, "Closing current case {0} in {1}", new Object[]{caseName, caseDir}); //NON-NLS
590  currentCase = null;
591  closedCase.close();
592  } finally {
593  /*
594  * The case is no longer the current case, even if an exception
595  * was thrown.
596  */
597  logger.log(Level.INFO, "Closed current case {0} in {1}", new Object[]{caseName, caseDir}); //NON-NLS
600  }
601  }
602  }
603  }
604 
616  public static void deleteCurrentCase() throws CaseActionException {
617  synchronized (currentCaseWriteLock) {
618  if (null == currentCase) {
619  return;
620  }
621  CaseMetadata metadata = currentCase.getCaseMetadata();
623  deleteCase(metadata);
624  }
625  }
626 
638  @Messages({
639  "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}",
640  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
641  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first",
642  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
643  "Case.progressMessage.deletingTextIndex=Deleting text index...",
644  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
645  "Case.exceptionMessage.cancelled=Cancelled by user"
646  })
647  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
648  synchronized (currentCaseWriteLock) {
649  if (null != currentCase && 0 == metadata.getCaseDirectory().compareTo(metadata.getCaseDirectory())) {
650  throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()));
651  }
652  }
653 
654  /*
655  * Set up either a GUI progress indicator or a logging progress
656  * indicator.
657  */
658  ProgressIndicator progressIndicator;
660  progressIndicator = new ModalDialogProgressIndicator(
661  mainFrame,
662  Bundle.Case_progressIndicatorTitle_deletingCase());
663  } else {
664  progressIndicator = new LoggingProgressIndicator();
665  }
666  progressIndicator.start(Bundle.Case_progressMessage_preparing());
667 
668  logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
669  ExecutorService executor = Executors.newSingleThreadExecutor();
670  Future<Void> future = executor.submit(() -> {
671  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
672  cleanupDeletedCase(metadata, progressIndicator);
673  } else {
674  /*
675  * First, acquire an exclusive case directory lock. The case
676  * cannot be deleted if another node has it open.
677  */
678  progressIndicator.start(Bundle.Case_progressMessage_checkingForOtherUser());
680  assert (null != dirLock);
681 
682  /*
683  * Delete the text index.
684  */
685  progressIndicator.start(Bundle.Case_progressMessage_deletingTextIndex());
686  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
687  searchService.deleteTextIndex(metadata);
688  }
689 
690  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
691  /*
692  * Delete the case database from the database server.
693  * The case database for a single-user case is in the
694  * case directory and will be deleted whe it is deleted.
695  */
696  progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase());
698  Class.forName("org.postgresql.Driver"); //NON-NLS
699  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
700  Statement statement = connection.createStatement();) {
701  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
702  statement.execute(deleteCommand);
703  }
704  }
705 
706  cleanupDeletedCase(metadata, progressIndicator);
707  }
708  }
709  return null;
710  });
711 
712  try {
713  future.get();
714  logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
715  } catch (InterruptedException ex) {
716  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
717  } catch (ExecutionException ex) {
718  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
719  } catch (CancellationException ex) {
720  throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
721  } finally {
722  executor.shutdown();
723  }
724  }
725 
734  private static String displayNameToUniqueName(String caseDisplayName) {
735  /*
736  * Replace all non-ASCII characters.
737  */
738  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
739 
740  /*
741  * Replace all control characters.
742  */
743  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
744 
745  /*
746  * Replace /, \, :, ?, space, ' ".
747  */
748  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
749 
750  /*
751  * Make it all lowercase.
752  */
753  uniqueCaseName = uniqueCaseName.toLowerCase();
754 
755  /*
756  * Add a time stamp for uniqueness.
757  */
758  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
759  Date date = new Date();
760  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
761 
762  return uniqueCaseName;
763  }
764 
773  static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
774 
775  File caseDirF = new File(caseDir);
776 
777  if (caseDirF.exists()) {
778  if (caseDirF.isFile()) {
779  throw new CaseActionException(
780  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
781 
782  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
783  throw new CaseActionException(
784  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
785  }
786  }
787 
788  try {
789  boolean result = (caseDirF).mkdirs(); // create root case Directory
790 
791  if (result == false) {
792  throw new CaseActionException(
793  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
794  }
795 
796  // create the folders inside the case directory
797  String hostClause = "";
798 
799  if (caseType == CaseType.MULTI_USER_CASE) {
800  hostClause = File.separator + NetworkUtils.getLocalHostName();
801  }
802  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
803  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
804  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
805  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
806 
807  if (result == false) {
808  throw new CaseActionException(
809  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
810  }
811 
812  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
813  result = new File(modulesOutDir).mkdir();
814 
815  if (result == false) {
816  throw new CaseActionException(
817  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
818  modulesOutDir));
819  }
820 
821  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
822  result = new File(reportsOutDir).mkdir();
823 
824  if (result == false) {
825  throw new CaseActionException(
826  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
827  modulesOutDir));
828 
829  }
830 
831  } catch (MissingResourceException | CaseActionException e) {
832  throw new CaseActionException(
833  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
834  }
835  }
836 
844  static Map<Long, String> getImagePaths(SleuthkitCase db) {
845  Map<Long, String> imgPaths = new HashMap<>();
846  try {
847  Map<Long, List<String>> imgPathsList = db.getImagePaths();
848  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
849  if (entry.getValue().size() > 0) {
850  imgPaths.put(entry.getKey(), entry.getValue().get(0));
851  }
852  }
853  } catch (TskCoreException ex) {
854  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
855  }
856  return imgPaths;
857  }
858 
867  @Messages({
868  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
869  })
870  private static void getMainWindowAndAppName() throws CaseActionException {
871  if (RuntimeProperties.runningWithGUI() && null == mainFrame) {
872  try {
873  SwingUtilities.invokeAndWait(() -> {
874  mainFrame = WindowManager.getDefault().getMainWindow();
875  /*
876  * This is tricky and fragile. What looks like lazy
877  * initialization of the appName field is actually getting
878  * the application name from the main window title BEFORE a
879  * case has been opened and a case name has been included in
880  * the title. It is also very specific to the desktop GUI.
881  *
882  * TODO (JIRA-2231): Make the application name a
883  * RuntimeProperties item set by Installers.
884  */
885  appName = mainFrame.getTitle();
886  });
887  } catch (InterruptedException | InvocationTargetException ex) {
888  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(Bundle.Case_exceptionMessage_cannotLocateMainWindow()), ex);
889  }
890  }
891  }
892 
900  @Messages({
901  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
902  })
903  private static void cleanupDeletedCase(CaseMetadata metadata, ProgressIndicator progressIndicator) {
904  /*
905  * Delete the case directory.
906  */
907  progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDirectory());
908  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
909  logger.log(Level.SEVERE, "Failed to fully delete case directory {0}", metadata.getCaseDirectory());
910  }
911 
912  /*
913  * If running in a GUI, remove the case from the Recent Cases menu
914  */
916  SwingUtilities.invokeLater(() -> {
917  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
918  });
919  }
920  }
921 
932  @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
934  try {
935  String resourcesNodeName = caseDir + "_resources";
936  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
937  if (null == lock) {
938  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
939  }
940  return lock;
941 
942  } catch (InterruptedException | CoordinationServiceException ex) {
943  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
944  }
945  }
946 
950  private static void updateGUIForCaseOpened(Case newCurrentCase) {
951  SwingUtilities.invokeLater(() -> {
952  /*
953  * If the case database was upgraded for a new schema and a backup
954  * database was created, notify the user.
955  */
956  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
957  String backupDbPath = caseDb.getBackupDatabasePath();
958  if (null != backupDbPath) {
959  JOptionPane.showMessageDialog(
960  WindowManager.getDefault().getMainWindow(),
961  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
962  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
963  JOptionPane.INFORMATION_MESSAGE);
964  }
965 
966  /*
967  * Look for the files for the data sources listed in the case
968  * database and give the user the opportunity to locate any that are
969  * missing.
970  */
971  Map<Long, String> imgPaths = getImagePaths(caseDb);
972  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
973  long obj_id = entry.getKey();
974  String path = entry.getValue();
975  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
976 
977  if (!fileExists) {
978  int response = JOptionPane.showConfirmDialog(
979  WindowManager.getDefault().getMainWindow(),
980  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", appName, path),
981  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
982  JOptionPane.YES_NO_OPTION);
983  if (response == JOptionPane.YES_OPTION) {
984  MissingImageDialog.makeDialog(obj_id, caseDb);
985  } else {
986  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
987 
988  }
989  }
990  }
991 
992  /*
993  * Enable the case-specific actions.
994  */
995  CallableSystemAction.get(AddImageAction.class
996  ).setEnabled(true);
997  CallableSystemAction
998  .get(CaseCloseAction.class
999  ).setEnabled(true);
1000  CallableSystemAction
1001  .get(CasePropertiesAction.class
1002  ).setEnabled(true);
1003  CallableSystemAction
1004  .get(CaseDeleteAction.class
1005  ).setEnabled(true);
1006  CallableSystemAction
1007  .get(OpenTimelineAction.class
1008  ).setEnabled(true);
1009  CallableSystemAction
1010  .get(OpenOutputFolderAction.class
1011  ).setEnabled(false);
1012 
1013  /*
1014  * Add the case to the recent cases tracker that supplies a list of
1015  * recent cases to the recent cases menu item and the open/create
1016  * case dialog.
1017  */
1018  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getCaseMetadata().getFilePath().toString());
1019 
1020  /*
1021  * Open the top components (windows within the main application
1022  * window).
1023  */
1024  if (newCurrentCase.hasData()) {
1026  }
1027 
1028  /*
1029  * Reset the main window title to be [curent case display name] -
1030  * [application name], instead of just the application name.
1031  */
1032  addCaseNameToMainWindowTitle(newCurrentCase.getDisplayName());
1033  });
1034  }
1035 
1036  /*
1037  * Update the GUI to to reflect the lack of a current case.
1038  */
1039  private static void updateGUIForCaseClosed() {
1040  SwingUtilities.invokeLater(() -> {
1041 
1042  /*
1043  * Close the top components (windows within the main application
1044  * window).
1045  */
1047 
1048  /*
1049  * Disable the case-specific menu items.
1050  */
1051  CallableSystemAction
1052  .get(AddImageAction.class
1053  ).setEnabled(false);
1054  CallableSystemAction
1055  .get(CaseCloseAction.class
1056  ).setEnabled(false);
1057  CallableSystemAction
1058  .get(CasePropertiesAction.class
1059  ).setEnabled(false);
1060  CallableSystemAction
1061  .get(CaseDeleteAction.class
1062  ).setEnabled(false);
1063  CallableSystemAction
1064  .get(OpenTimelineAction.class
1065  ).setEnabled(false);
1066  CallableSystemAction
1067  .get(OpenOutputFolderAction.class
1068  ).setEnabled(false);
1069 
1070  /*
1071  * Clear the notifications in the notfier component in the lower
1072  * right hand corner of the main application window.
1073  */
1075 
1076  /*
1077  * Reset the main window title to be just the application name,
1078  * instead of [curent case display name] - [application name].
1079  */
1080  Frame mainWindow = WindowManager.getDefault().getMainWindow();
1081  mainWindow.setTitle(appName);
1082  });
1083  }
1084 
1090  private static void addCaseNameToMainWindowTitle(String caseName) {
1091  if (!caseName.isEmpty()) {
1092  Frame frame = WindowManager.getDefault().getMainWindow();
1093  frame.setTitle(caseName + " - " + appName);
1094  }
1095  }
1096 
1100  private static void clearTempSubDir(String tempSubDirPath) {
1101  File tempFolder = new File(tempSubDirPath);
1102  if (tempFolder.isDirectory()) {
1103  File[] files = tempFolder.listFiles();
1104  if (files.length > 0) {
1105  for (File file : files) {
1106  if (file.isDirectory()) {
1107  FileUtil.deleteDir(file);
1108  } else {
1109  file.delete();
1110  }
1111  }
1112  }
1113  }
1114  }
1115 
1122  return this.caseDb;
1123  }
1124 
1131  return caseServices;
1132  }
1133 
1139  public CaseType getCaseType() {
1140  return getCaseMetadata().getCaseType();
1141  }
1142 
1148  public String getCreatedDate() {
1149  return getCaseMetadata().getCreatedDate();
1150  }
1151 
1157  public String getName() {
1158  return getCaseMetadata().getCaseName();
1159  }
1160 
1166  public String getDisplayName() {
1167  return getCaseMetadata().getCaseDisplayName();
1168  }
1169 
1175  public String getNumber() {
1176  return caseMetadata.getCaseNumber();
1177  }
1178 
1184  public String getExaminer() {
1185  return caseMetadata.getExaminer();
1186  }
1187 
1193  public String getCaseDirectory() {
1194  return caseMetadata.getCaseDirectory();
1195  }
1196 
1205  public String getOutputDirectory() {
1206  String caseDirectory = getCaseDirectory();
1207  Path hostPath;
1208  if (getCaseMetadata().getCaseType() == CaseType.MULTI_USER_CASE) {
1209  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1210  } else {
1211  hostPath = Paths.get(caseDirectory);
1212  }
1213  if (!hostPath.toFile().exists()) {
1214  hostPath.toFile().mkdirs();
1215  }
1216  return hostPath.toString();
1217  }
1218 
1225  public String getTempDirectory() {
1226  return getOrCreateSubdirectory(TEMP_FOLDER);
1227  }
1228 
1235  public String getCacheDirectory() {
1236  return getOrCreateSubdirectory(CACHE_FOLDER);
1237  }
1238 
1245  public String getExportDirectory() {
1246  return getOrCreateSubdirectory(EXPORT_FOLDER);
1247  }
1248 
1255  public String getLogDirectoryPath() {
1256  return getOrCreateSubdirectory(LOG_FOLDER);
1257  }
1258 
1265  public String getReportDirectory() {
1266  return getOrCreateSubdirectory(REPORTS_FOLDER);
1267  }
1268 
1275  public String getModuleDirectory() {
1276  return getOrCreateSubdirectory(MODULE_FOLDER);
1277  }
1278 
1287  Path path = Paths.get(getModuleDirectory());
1288  if (getCaseType() == CaseType.MULTI_USER_CASE) {
1289  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1290  } else {
1291  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1292  }
1293  }
1294 
1304  public List<Content> getDataSources() throws TskCoreException {
1305  List<Content> list = caseDb.getRootObjects();
1306  hasDataSources = (list.size() > 0);
1307  return list;
1308  }
1309 
1315  public Set<TimeZone> getTimeZones() {
1316  Set<TimeZone> timezones = new HashSet<>();
1317  try {
1318  for (Content c : getDataSources()) {
1319  final Content dataSource = c.getDataSource();
1320  if ((dataSource != null) && (dataSource instanceof Image)) {
1321  Image image = (Image) dataSource;
1322  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1323  }
1324  }
1325  } catch (TskCoreException ex) {
1326  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1327  }
1328  return timezones;
1329  }
1330 
1338  public void setTextIndexName(String textIndexName) throws CaseMetadataException {
1339  getCaseMetadata().setTextIndexName(textIndexName);
1340  }
1341 
1347  public String getTextIndexName() {
1348  return getCaseMetadata().getTextIndexName();
1349  }
1350 
1357  public boolean hasData() {
1358  if (!hasDataSources) {
1359  try {
1360  hasDataSources = (getDataSources().size() > 0);
1361  } catch (TskCoreException ex) {
1362  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1363  }
1364  }
1365  return hasDataSources;
1366  }
1367 
1378  public void notifyAddingDataSource(UUID eventId) {
1379  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1380  }
1381 
1392  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1393  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1394  }
1395 
1407  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1408  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1409  }
1410 
1418  public void notifyContentTagAdded(ContentTag newTag) {
1419  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1420  }
1421 
1429  public void notifyContentTagDeleted(ContentTag deletedTag) {
1430  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1431  }
1432 
1441  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1442  }
1443 
1452  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1453  }
1454 
1466  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1467  String normalizedLocalPath;
1468  try {
1469  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1470  } catch (InvalidPathException ex) {
1471  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1472  throw new TskCoreException(errorMsg, ex);
1473  }
1474  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName);
1475  eventPublisher.publish(new ReportAddedEvent(report));
1476  }
1477 
1486  public List<Report> getAllReports() throws TskCoreException {
1487  return this.caseDb.getAllReports();
1488  }
1489 
1498  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1499  for (Report report : reports) {
1500  this.caseDb.deleteReport(report);
1501  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1502  }
1503  }
1504 
1510  CaseMetadata getCaseMetadata() {
1511  return caseMetadata;
1512  }
1513 
1522  void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
1523  try {
1524  caseMetadata.setCaseDisplayName(newCaseName);
1525  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName));
1526  SwingUtilities.invokeLater(() -> {
1527  try {
1528  RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath);
1529  addCaseNameToMainWindowTitle(newCaseName);
1530 
1531  } catch (Exception ex) {
1532  Logger.getLogger(Case.class
1533  .getName()).log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1534  }
1535  });
1536  } catch (CaseMetadataException ex) {
1537  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), ex);
1538  }
1539  }
1540 
1544  private Case() {
1545  }
1546 
1564  @Messages({
1565  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1566  "Case.progressIndicatorCancelButton.label=Cancel",
1567  "Case.progressMessage.preparing=Preparing...",
1568  "Case.progressMessage.openingCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>"
1569  })
1570  private void create(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
1571  /*
1572  * Set up either a GUI progress indicator or a logging progress
1573  * indicator.
1574  */
1575  final CancelButtonListener listener = new CancelButtonListener();
1576  ProgressIndicator progressIndicator;
1578  progressIndicator = new ModalDialogProgressIndicator(
1579  mainFrame,
1580  Bundle.Case_progressIndicatorTitle_creatingCase(),
1581  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1582  Bundle.Case_progressIndicatorCancelButton_label(),
1583  listener);
1584  } else {
1585  progressIndicator = new LoggingProgressIndicator();
1586  }
1587  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1588 
1589  /*
1590  * Creating a case is always done in the same non-UI thread that will be
1591  * used later to close the case. If the case is a multi-user case, this
1592  * ensures that case directory lock that is held as long as the case is
1593  * open is released in the same thread in which it was acquired, as is
1594  * required by the coordination service.
1595  */
1596  caseLockingExecutor = Executors.newSingleThreadExecutor();
1597  Future<Void> future = caseLockingExecutor.submit(() -> {
1598  if (CaseType.SINGLE_USER_CASE == caseType) {
1599  create(caseType, caseDir, caseDisplayName, caseNumber, examiner, progressIndicator);
1600  } else {
1601  /*
1602  * Acquire a shared case directory lock that will be held as
1603  * long as this node has this case open. This will prevent
1604  * deletion of the case by another node.
1605  */
1606  progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources());
1607  acquireSharedCaseDirLock(caseDir);
1608 
1609  /*
1610  * Acquire an exclusive case resources lock to ensure only one
1611  * node at a time can create/open/upgrade/close the case
1612  * resources.
1613  */
1614  try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseDir)) {
1615  assert (null != resourcesLock);
1616  try {
1617  create(caseType, caseDir, caseDisplayName, caseNumber, examiner, progressIndicator);
1618  } catch (CaseActionException ex) {
1619  /*
1620  * Release the case directory lock immediately if there
1621  * was a problem opening the case.
1622  */
1623  if (CaseType.MULTI_USER_CASE == caseType) {
1624  releaseSharedCaseDirLock(caseDir);
1625  }
1626  throw ex;
1627  }
1628  }
1629  }
1630  return null;
1631  });
1632 
1633  /*
1634  * If running with a GUI, give the future for the case creation task to
1635  * the cancel button listener for the GUI progress indicator and make
1636  * the progress indicator visible to the user.
1637  */
1639  listener.setCaseActionFuture(future);
1640  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
1641  }
1642 
1643  /*
1644  * Wait for the case creation task to finish.
1645  */
1646  try {
1647  future.get();
1648  } catch (InterruptedException ex) {
1649  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
1650  } catch (ExecutionException ex) {
1651  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
1652  } catch (CancellationException ex) {
1653  throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
1654  } finally {
1655  if (RuntimeProperties.runningWithGUI()) {
1656  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
1657  }
1658  }
1659  }
1660 
1682  @Messages({
1683  "Case.exceptionMessage.emptyCaseName=Case name is empty.",
1684  "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1685  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1686  "Case.exceptionMessage.couldNotCreateCaseDatabaseName=Failed to create case database name from case name.",
1687  "Case.progressMessage.creatingCaseMetadataFile=Creating case metadata file...",
1688  "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file.",
1689  "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database."
1690  })
1691  private void create(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner, ProgressIndicator progressIndicator) throws CaseActionException {
1692  /*
1693  * Create a unique and immutable case name from the case display name.
1694  */
1695  if (caseDisplayName.isEmpty()) {
1696  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
1697  }
1698  String caseName = displayNameToUniqueName(caseDisplayName);
1699 
1700  /*
1701  * Create the case directory, if it does not already exist.
1702  *
1703  * TODO (JIRA-2180): The reason for this check for the existence of the
1704  * case directory is not at all obvious. It reflects the assumption that
1705  * if the case directory already exists, it is because the case is being
1706  * created using the the "New Case" wizard, which separates the creation
1707  * of the case directory from the creation of the case, with the idea
1708  * that if the case directory cannot be created, the user can be asked
1709  * to supply a different case directory path. This of course creates
1710  * subtle and undetectable coupling between this code and the wizard
1711  * code. The desired effect could be accomplished more simply and safely
1712  * by having this method throw a specific exception to indicate that the
1713  * case directory could not be created. In fact, a FEW specific
1714  * exception types would in turn allow us to put localized,
1715  * user-friendly messages in the GUI instead of putting user-friendly,
1716  * localized messages in the exceptions, which causes them to appear in
1717  * the application log, where it would be better to use English for
1718  * readability by the broadest group of developers.
1719  */
1720  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1721  if (new File(caseDir).exists() == false) {
1722  Case.createCaseDirectory(caseDir, caseType);
1723  }
1724 
1725  /*
1726  * Create the case database.
1727  */
1728  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1729  String dbName = null;
1730  try {
1731  if (CaseType.SINGLE_USER_CASE == caseType) {
1732  /*
1733  * For single-user cases, the case database is a SQLite database
1734  * with a standard name, physically located in the root of the
1735  * case directory.
1736  */
1737  dbName = SINGLE_USER_CASE_DB_NAME;
1738  this.caseDb = SleuthkitCase.newCase(Paths.get(caseDir, SINGLE_USER_CASE_DB_NAME).toString());
1739  } else if (CaseType.MULTI_USER_CASE == caseType) {
1740  /*
1741  * For multi-user cases, the case database is a PostgreSQL
1742  * database with a name derived from the case display name,
1743  * physically located on the database server.
1744  */
1745  this.caseDb = SleuthkitCase.newCase(caseDisplayName, UserPreferences.getDatabaseConnectionInfo(), caseDir);
1746  dbName = this.caseDb.getDatabaseName();
1747  }
1748  } catch (TskCoreException ex) {
1749  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(), ex);
1750  } catch (UserPreferencesException ex) {
1751  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1752  }
1753 
1754  /*
1755  * Create the case metadata (.aut) file.
1756  */
1757  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseMetadataFile());
1758  try {
1759  this.caseMetadata = new CaseMetadata(caseDir, caseType, caseName, caseDisplayName, caseNumber, examiner, dbName);
1760  } catch (CaseMetadataException ex) {
1761  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1762  }
1763 
1764  openServices(progressIndicator);
1765  }
1766 
1777  private void open(Path caseMetadataFilePath) throws CaseActionException {
1778  /*
1779  * Read the contents of the case metadata file.
1780  */
1781  try {
1782  caseMetadata = new CaseMetadata(caseMetadataFilePath);
1783  } catch (CaseMetadataException ex) {
1784  throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex);
1785  }
1786 
1787  /*
1788  * Set up either a GUI progress indicator or a logging progress
1789  * indicator.
1790  */
1791  CancelButtonListener listener = new CancelButtonListener();
1792  ProgressIndicator progressIndicator;
1794  progressIndicator = new ModalDialogProgressIndicator(
1795  mainFrame,
1796  Bundle.Case_progressIndicatorTitle_openingCase(),
1797  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1798  Bundle.Case_progressIndicatorCancelButton_label(),
1799  listener);
1800  } else {
1801  progressIndicator = new LoggingProgressIndicator();
1802  }
1803  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1804 
1805  /*
1806  * Opening the case in the same thread that will be used later to close
1807  * the case. If the case is a multi-user case, this ensures that case
1808  * directory lock that is held as long as the case is open is released
1809  * in the same thread in which it was acquired, as is required by the
1810  * coordination service.
1811  */
1812  CaseType caseType = caseMetadata.getCaseType();
1813  String caseName = caseMetadata.getCaseName();
1814  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1815  caseLockingExecutor = Executors.newSingleThreadExecutor();
1816  Future<Void> future = caseLockingExecutor.submit(() -> {
1817  if (CaseType.SINGLE_USER_CASE == caseType) {
1818  openCaseDatabase(progressIndicator);
1819  openServices(progressIndicator);
1820  } else {
1821  /*
1822  * First, acquire a shared case directory lock that will be held
1823  * as long as this node has this case open, in order to prevent
1824  * deletion of the case by another node.
1825  */
1826  progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources());
1827  acquireSharedCaseDirLock(caseMetadata.getCaseDirectory());
1828  /*
1829  * Next, acquire an exclusive case resources lock to ensure only
1830  * one node at a time can create/open/upgrade/close case
1831  * resources.
1832  */
1833  try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseDirectory())) {
1834  assert (null != resourcesLock);
1835  try {
1836  openCaseDatabase(progressIndicator);
1837  openServices(progressIndicator);
1838  } catch (CaseActionException ex) {
1839  /*
1840  * Release the case directory lock immediately if there
1841  * was a problem opening the case.
1842  */
1843  if (CaseType.MULTI_USER_CASE == caseType) {
1844  releaseSharedCaseDirLock(caseName);
1845  }
1846  throw ex;
1847  }
1848  }
1849  }
1850  return null;
1851  });
1852 
1853  /*
1854  * If running with a GUI, give the future for the case opening task to
1855  * the cancel button listener for the GUI progress indicator and make
1856  * the progress indicator visible to the user.
1857  */
1859  listener.setCaseActionFuture(future);
1860  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
1861  }
1862 
1863  /*
1864  * Wait for the case opening task to finish.
1865  */
1866  try {
1867  future.get();
1868 
1869  } catch (InterruptedException ex) {
1870  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
1871  } catch (ExecutionException ex) {
1872  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
1873  } catch (CancellationException ex) {
1874  throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
1875  } finally {
1876  if (RuntimeProperties.runningWithGUI()) {
1877  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
1878  }
1879  }
1880  }
1881 
1892  @Messages({
1893  "Case.progressMessage.openingCaseDatabase=Opening case database...",
1894  "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database."
1895  })
1896  private void openCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
1897  /*
1898  * Open the case database.
1899  */
1900  try {
1901  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
1902  if (CaseType.SINGLE_USER_CASE == caseMetadata.getCaseType()) {
1903  this.caseDb = SleuthkitCase.openCase(Paths.get(caseMetadata.getCaseDirectory(), caseMetadata.getCaseDatabaseName()).toString());
1905  try {
1906  this.caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
1907 
1908  } catch (UserPreferencesException ex) {
1909  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1910 
1911  }
1912  } else {
1913  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
1914  }
1915  } catch (TskCoreException ex) {
1916  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
1917  }
1918  }
1919 
1928  @Messages({
1929  "Case.progressMessage.switchingLogDirectory=Switching log directory...",
1930  "Case.progressMessage.settingUpTskErrorReporting=Setting up SleuthKit error reporting...",
1931  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
1932  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
1933  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
1934  private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
1935  /*
1936  * Switch to writing to the application logs in the logs subdirectory of
1937  * the case directory.
1938  */
1939  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
1940  Logger.setLogDirectory(getLogDirectoryPath());
1941 
1942  /*
1943  * Hook up a SleuthKit layer error reporter.
1944  */
1945  progressIndicator.progress(Bundle.Case_progressMessage_settingUpTskErrorReporting());
1946  this.sleuthkitErrorReporter = new SleuthkitErrorReporter(MIN_SECS_BETWEEN_TSK_ERROR_REPORTS, NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
1947  this.caseDb.addErrorObserver(this.sleuthkitErrorReporter);
1948 
1949  /*
1950  * Clear the temp subdirectory of the case directory.
1951  */
1952  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
1953  Case.clearTempSubDir(this.getTempDirectory());
1954 
1955  /*
1956  * Open the case-level services.
1957  */
1958  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
1959  this.caseServices = new Services(this.caseDb);
1960 
1961  /*
1962  * Allow any registered application services to open any resources
1963  * specific to this case.
1964  */
1965  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
1966  openAppServiceCaseResources();
1967 
1968  /*
1969  * If this case is a multi-user case, set up for communication with
1970  * other nodes.
1971  */
1972  if (CaseType.MULTI_USER_CASE == this.caseMetadata.getCaseType()) {
1973  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
1974  try {
1975  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, this.getName()));
1976  collaborationMonitor = new CollaborationMonitor(this.getName());
1977  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
1978  /*
1979  * The collaboration monitor and event channel are not
1980  * essential. Log an error and notify the user, but do not
1981  * throw.
1982  */
1983  logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
1984 
1986  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
1987  }
1988  }
1989  }
1990  }
1991 
1998  @NbBundle.Messages({
1999  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2000  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error",
2001  "# {0} - service name", "Case.servicesException.serviceResourcesOpenCancelled=Opening case resources for {0} cancelled",
2002  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesOpenError=Could not open case resources for {0} service: {1}"
2003  })
2005  /*
2006  * Each service gets its own independently cancellable task, and thus
2007  * its own task progress indicator.
2008  */
2009  ExecutorService executor = Executors.newSingleThreadExecutor();
2010 
2011  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2012  CancelButtonListener buttonListener = new CancelButtonListener();
2013  ProgressIndicator progressIndicator;
2015  progressIndicator = new ModalDialogProgressIndicator(
2016  mainFrame,
2017  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2018  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2019  Bundle.Case_progressIndicatorCancelButton_label(),
2020  buttonListener);
2021  } else {
2022  progressIndicator = new LoggingProgressIndicator();
2023  }
2024  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2025 
2026  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2028  buttonListener.setCaseContext(context);
2029  }
2030  Future<Void> future = executor.submit(() -> {
2031  service.openCaseResources(context);
2032  return null;
2033  });
2035  buttonListener.setCaseActionFuture(future);
2036  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
2037  }
2038  try {
2039  future.get();
2040  } catch (InterruptedException ex) {
2041  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to open case resources", service.getServiceName()), ex);
2042 
2043  } catch (CancellationException ex) {
2044  /*
2045  * The case-specific application service resources are not
2046  * essential. Log an error and notify the user if running the
2047  * desktop GUI, but do not throw.
2048  */
2049  Case.logger.log(Level.WARNING, String.format("%s service opening of case resources cancelled", service.getServiceName()));
2051  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.warn(
2052  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2053  Bundle.Case_servicesException_serviceResourcesOpenCancelled(service.getServiceName())));
2054  }
2055  } catch (ExecutionException ex) {
2056  /*
2057  * The case-specific application service resources are not
2058  * essential. Log an error and notify the user if running the
2059  * desktop GUI, but do not throw.
2060  */
2061  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2063  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2064  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2065  Bundle.Case_servicesException_serviceResourcesOpenError(service.getServiceName(), ex.getLocalizedMessage())));
2066  }
2067  } finally {
2069  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
2070  }
2071  }
2072  }
2073  /*
2074  * No tasks left, simply shut down the executor.
2075  */
2076  executor.shutdown();
2077  }
2078 
2084  @Messages({
2085  "Case.progressMessage.closingCaseResources=<html>Preparing to close case resources.<br>This may take time if another user is upgrading the case.</html>",
2086  "Case.progressMessage.notifyingCaseEventSubscribers=Notifying case event subscribers...",
2087  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
2088  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2089  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2090  "Case.progressMessage.tearingDownNetworkCommunications=Tearing down network communications...",
2091  "Case.progressMessage.closingCaseDatabase=Closing case database...",
2092  "Case.progressMessage.tearingDownTskErrorReporting=Tearing down SleuthKit error reporting..."
2093  })
2094  private void close() throws CaseActionException {
2095  /*
2096  * Set up either a GUI progress indicator or a logging progress
2097  * indicator.
2098  */
2099  ProgressIndicator progressIndicator;
2101  progressIndicator = new ModalDialogProgressIndicator(
2102  Case.mainFrame,
2103  Bundle.Case_progressIndicatorTitle_closingCase());
2104  } else {
2105  progressIndicator = new LoggingProgressIndicator();
2106  }
2107  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2108 
2109  /*
2110  * Closing a case is always done in the same non-UI thread that
2111  * opened/created the case. If the case is a multi-user case, this
2112  * ensures that case directory lock that is held as long as the case is
2113  * open is released in the same thread in which it was acquired, as is
2114  * required by the coordination service.
2115  */
2116  Future<Void> future = caseLockingExecutor.submit(() -> {
2117  if (CaseType.SINGLE_USER_CASE == caseMetadata.getCaseType()) {
2118  close(progressIndicator);
2119  } else {
2120  String caseName = caseMetadata.getCaseName();
2121  /*
2122  * Acquire an exclusive case resources lock to ensure only one
2123  * node at a time can create/open/upgrade/close the case
2124  * resources.
2125  */
2126  progressIndicator.start(Bundle.Case_progressMessage_closingCaseResources());
2127  try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseDirectory())) {
2128  assert (null != resourcesLock);
2129  close(progressIndicator);
2130  } finally {
2131  /*
2132  * Always release the case directory lock that was acquired
2133  * when the case was opened.
2134  */
2135  releaseSharedCaseDirLock(caseName);
2136  }
2137  }
2138  return null;
2139  });
2140 
2141  /*
2142  * If running with a GUI, give the future for the case closing task to
2143  * the cancel button listener for the GUI progress indicator and make
2144  * the progress indicator visible to the user.
2145  */
2147  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
2148  }
2149 
2150  try {
2151  logger.log(Level.INFO, "Closing case with metadata file path {0}", getCaseMetadata().getFilePath()); //NON-NLS
2152  future.get();
2153  } catch (InterruptedException ex) {
2154  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
2155  } catch (ExecutionException ex) {
2156  throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
2157  } catch (CancellationException ex) {
2158  throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
2159  } finally {
2160  caseLockingExecutor.shutdown();
2162  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
2163  }
2164  }
2165  }
2166 
2167  private void close(ProgressIndicator progressIndicator) {
2169 
2170  /*
2171  * Stop sending/receiving case events to and from other nodes if this is
2172  * a multi-user case.
2173  */
2174  if (CaseType.MULTI_USER_CASE == caseMetadata.getCaseType()) {
2175  progressIndicator.progress(Bundle.Case_progressMessage_tearingDownNetworkCommunications());
2176  if (null != collaborationMonitor) {
2177  collaborationMonitor.shutdown();
2178  }
2179  eventPublisher.closeRemoteEventChannel();
2180  }
2181 
2182  /*
2183  * Allow all registered application services providers to close
2184  * resources related to the case.
2185  */
2186  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2187  closeAppServiceCaseResources();
2188 
2189  /*
2190  * Close the case-level services.
2191  */
2192  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2193  try {
2194  this.caseServices.close();
2195  } catch (IOException ex) {
2196  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2197  }
2198 
2199  /*
2200  * Close the case database
2201  */
2202  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2203  caseDb.close();
2204 
2205  /*
2206  * Disconnect the SleuthKit layer error reporter.
2207  */
2208  progressIndicator.progress(Bundle.Case_progressMessage_tearingDownTskErrorReporting());
2209  caseDb.removeErrorObserver(sleuthkitErrorReporter);
2210 
2211  /*
2212  * Switch the log directory.
2213  */
2214  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2216  }
2217 
2222  @Messages({
2223  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2224  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2225  })
2227  /*
2228  * Each service gets its own independently cancellable task, and thus
2229  * its own task progress indicator.
2230  */
2231  ExecutorService executor = Executors.newSingleThreadExecutor();
2232 
2233  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2234  )) {
2235  ProgressIndicator progressIndicator;
2237  progressIndicator = new ModalDialogProgressIndicator(
2238  mainFrame,
2239  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2240  } else {
2241  progressIndicator = new LoggingProgressIndicator();
2242  }
2243  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2244 
2245  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2246  Future<Void> future = executor.submit(() -> {
2247  service.closeCaseResources(context);
2248  return null;
2249  });
2251  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
2252  }
2253  try {
2254  future.get();
2255  } catch (InterruptedException ex) {
2256  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2257 
2258  } catch (CancellationException ex) {
2259  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2260 
2261  } catch (ExecutionException ex) {
2262  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2264  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2265  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2266  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2267  }
2268  } finally {
2270  SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
2271  }
2272  }
2273  }
2274  /*
2275  * No tasks left, simply shut down the executor.
2276  */
2277  executor.shutdown();
2278  }
2279 
2288  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2289  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2290  try {
2291  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2292  if (null == caseDirLock) {
2293  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2294  }
2295  } catch (InterruptedException | CoordinationServiceException ex) {
2296  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2297  }
2298  }
2299 
2305  private void releaseSharedCaseDirLock(String caseDir) {
2306  if (caseDirLock != null) {
2307  try {
2308  caseDirLock.release();
2309  caseDirLock = null;
2311  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2312  }
2313  }
2314  }
2315 
2322  private String getOrCreateSubdirectory(String subDirectoryName) {
2323  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2324  if (!subDirectory.exists()) {
2325  subDirectory.mkdirs();
2326  }
2327  return subDirectory.toString();
2328 
2329  }
2330 
2331  private final static class CancelButtonListener implements ActionListener {
2332 
2333  private Future<?> caseActionFuture;
2335 
2336  private void setCaseActionFuture(Future<?> caseActionFuture) {
2337  this.caseActionFuture = caseActionFuture;
2338  }
2339 
2340  private void setCaseContext(CaseContext caseContext) {
2341  this.caseContext = caseContext;
2342  }
2343 
2344  @Override
2345  public void actionPerformed(ActionEvent e) {
2346  if (null != this.caseContext) {
2347  this.caseContext.requestCancel();
2348  }
2349  if (null != this.caseActionFuture) {
2350  this.caseActionFuture.cancel(true);
2351  }
2352  }
2353 
2354  }
2355 
2375  @Deprecated
2376  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2377  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2378  }
2379 
2400  @Deprecated
2401  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2402  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2403  }
2404 
2416  @Deprecated
2417  public static void open(String caseMetadataFilePath) throws CaseActionException {
2418  openAsCurrentCase(caseMetadataFilePath);
2419  }
2420 
2430  @Deprecated
2431  public void closeCase() throws CaseActionException {
2432  closeCurrentCase();
2433  }
2434 
2440  @Deprecated
2441  public static void invokeStartupDialog() {
2443  }
2444 
2458  @Deprecated
2459  public static String convertTimeZone(String timeZoneId) {
2460  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2461  }
2462 
2472  @Deprecated
2473  public static boolean pathExists(String filePath) {
2474  return new File(filePath).isFile();
2475  }
2476 
2485  @Deprecated
2486  public static String getAutopsyVersion() {
2487  return Version.getVersion();
2488  }
2489 
2497  @Deprecated
2498  public static boolean existsCurrentCase() {
2499  return isCaseOpen();
2500  }
2501 
2511  @Deprecated
2512  public static String getModulesOutputDirRelPath() {
2513  return "ModuleOutput"; //NON-NLS
2514  }
2515 
2525  @Deprecated
2526  public static PropertyChangeSupport
2528  return new PropertyChangeSupport(Case.class
2529  );
2530  }
2531 
2540  @Deprecated
2541  public String getModulesOutputDirAbsPath() {
2542  return getModuleDirectory();
2543  }
2544 
2559  @Deprecated
2560  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
2561  try {
2562  Image newDataSource = caseDb.getImageById(imgId);
2563  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
2564  return newDataSource;
2565  } catch (TskCoreException ex) {
2566  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
2567  }
2568  }
2569 
2577  @Deprecated
2578  public Set<TimeZone> getTimeZone() {
2579  return getTimeZones();
2580  }
2581 
2592  @Deprecated
2593  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
2594  deleteReports(reports);
2595  }
2596 
2597 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:128
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1429
static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS
Definition: Case.java:125
static CaseType fromString(String typeName)
Definition: Case.java:189
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1451
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:2560
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:933
void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2336
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:384
void create(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:1570
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:117
static final String EXPORT_FOLDER
Definition: Case.java:121
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2459
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:121
static void addCaseNameToMainWindowTitle(String caseName)
Definition: Case.java:1090
static final String CACHE_FOLDER
Definition: Case.java:120
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1466
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:950
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void cleanupDeletedCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:903
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2401
void setTextIndexName(String textIndexName)
Definition: Case.java:1338
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:118
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:647
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:2593
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1100
static boolean isValidName(String caseName)
Definition: Case.java:438
void create(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner, ProgressIndicator progressIndicator)
Definition: Case.java:1691
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2289
CollaborationMonitor collaborationMonitor
Definition: Case.java:168
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2305
static String getModulesOutputDirRelPath()
Definition: Case.java:2512
void start(String message, int totalWorkUnits)
static final String MODULE_FOLDER
Definition: Case.java:126
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2376
void openCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:1896
void openServices(ProgressIndicator progressIndicator)
Definition: Case.java:1934
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:734
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:516
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:426
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:116
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2167
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1440
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2527
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:372
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:416
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1498
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1407
static boolean pathExists(String filePath)
Definition: Case.java:2473
SleuthkitErrorReporter sleuthkitErrorReporter
Definition: Case.java:167
static void open(String caseMetadataFilePath)
Definition: Case.java:2417
static final Object currentCaseWriteLock
Definition: Case.java:141
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2322
boolean equalsName(String otherTypeName)
Definition: Case.java:247
static final String EVENT_CHANNEL_NAME
Definition: Case.java:119
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
void open(Path caseMetadataFilePath)
Definition: Case.java:1777
static SleuthkitCase openCase(String dbPath)
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:122
static SleuthkitCase newCase(String dbPath)
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:143
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1378
CoordinationService.Lock caseDirLock
Definition: Case.java:164
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:406
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1418
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:469
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1392
static final String REPORTS_FOLDER
Definition: Case.java:123
static final String TEMP_FOLDER
Definition: Case.java:124
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:396

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