Autopsy  4.4
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;
105 import org.sleuthkit.datamodel.BlackboardArtifactTag;
106 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
107 import org.sleuthkit.datamodel.Content;
108 import org.sleuthkit.datamodel.ContentTag;
109 import org.sleuthkit.datamodel.Image;
110 import org.sleuthkit.datamodel.Report;
111 import org.sleuthkit.datamodel.SleuthkitCase;
112 import org.sleuthkit.datamodel.TskCoreException;
113 
117 public class Case {
118 
119  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
120  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
121  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
122  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
123  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
124  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
125  private static final String LOG_FOLDER = "Log"; //NON-NLS
126  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
127  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
128  private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60;
129  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
130  private static final long EXECUTOR_AWAIT_TIMEOUT_SECS = 5;
131  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
132  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
133  private static final Logger logger = Logger.getLogger(Case.class.getName());
135  private static final Object caseActionSerializationLock = new Object();
136  private static volatile Frame mainFrame;
137  private static volatile Case currentCase;
138  private final CaseMetadata metadata;
139  private volatile ExecutorService caseLockingExecutor;
141  private SleuthkitCase caseDb;
142  private CollaborationMonitor collaborationMonitor;
144  private boolean hasDataSources;
145 
146  /*
147  * Get a reference to the main window of the desktop application to use to
148  * parent pop up dialogs and initialize the application name for use in
149  * changing the main window title.
150  */
151  static {
152  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
153  @Override
154  public void run() {
155  mainFrame = WindowManager.getDefault().getMainWindow();
156  }
157  });
158  }
159 
163  public enum CaseType {
164 
165  SINGLE_USER_CASE("Single-user case"), //NON-NLS
166  MULTI_USER_CASE("Multi-user case"); //NON-NLS
167 
168  private final String typeName;
169 
177  public static CaseType fromString(String typeName) {
178  if (typeName != null) {
179  for (CaseType c : CaseType.values()) {
180  if (typeName.equalsIgnoreCase(c.toString())) {
181  return c;
182  }
183  }
184  }
185  return null;
186  }
187 
193  @Override
194  public String toString() {
195  return typeName;
196  }
197 
203  @Messages({
204  "Case_caseType_singleUser=Single-user case",
205  "Case_caseType_multiUser=Multi-user case"
206  })
208  if (fromString(typeName) == SINGLE_USER_CASE) {
209  return Bundle.Case_caseType_singleUser();
210  } else {
211  return Bundle.Case_caseType_multiUser();
212  }
213  }
214 
220  private CaseType(String typeName) {
221  this.typeName = typeName;
222  }
223 
234  @Deprecated
235  public boolean equalsName(String otherTypeName) {
236  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
237  }
238 
239  };
240 
245  public enum Events {
246 
349  };
350 
357  public static void addPropertyChangeListener(PropertyChangeListener listener) {
358  addEventSubscriber(Stream.of(Events.values())
359  .map(Events::toString)
360  .collect(Collectors.toSet()), listener);
361  }
362 
369  public static void removePropertyChangeListener(PropertyChangeListener listener) {
370  removeEventSubscriber(Stream.of(Events.values())
371  .map(Events::toString)
372  .collect(Collectors.toSet()), listener);
373  }
374 
381  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
382  eventPublisher.addSubscriber(eventNames, subscriber);
383  }
384 
391  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
392  eventPublisher.addSubscriber(eventName, subscriber);
393  }
394 
401  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
402  eventPublisher.removeSubscriber(eventName, subscriber);
403  }
404 
411  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
412  eventPublisher.removeSubscriber(eventNames, subscriber);
413  }
414 
423  public static boolean isValidName(String caseName) {
424  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
425  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
426  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
427  }
428 
450  @Messages({
451  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
452  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
453  })
454  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
455  if (caseDisplayName.isEmpty()) {
456  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
457  }
458  if (caseDir.isEmpty()) {
459  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
460  }
461  openAsCurrentCase(new Case(caseType, caseDir, caseDisplayName, caseNumber, examiner), true);
462  }
463 
477  @Messages({
478  "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
479  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
480  })
481  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
483  try {
484  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
485  } catch (CaseMetadataException ex) {
486  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex);
487  }
489  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
490  }
491  openAsCurrentCase(new Case(metadata), false);
492  }
493 
499  public static boolean isCaseOpen() {
500  return currentCase != null;
501  }
502 
510  public static Case getCurrentCase() {
511  /*
512  * Throwing an unchecked exception is a bad idea here.
513  *
514  * TODO (JIRA-2229): Case.getCurrentCase() method throws unchecked
515  * IllegalStateException; change to throw checked exception or return
516  * null
517  */
518  if (null != currentCase) {
519  return currentCase;
520  } else {
521  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
522  }
523  }
524 
533  @Messages({
534  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
535  "Case.progressIndicatorTitle.closingCase=Closing Case"
536  })
537  public static void closeCurrentCase() throws CaseActionException {
538  synchronized (caseActionSerializationLock) {
539  if (null == currentCase) {
540  return;
541  }
542  Case closedCase = currentCase;
543  try {
544  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
545  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
546  currentCase = null;
547  closedCase.close();
548  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
549  } catch (CaseActionException ex) {
550  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
551  throw ex;
552  } finally {
555  }
556  }
557  }
558  }
559 
568  public static void deleteCurrentCase() throws CaseActionException {
569  synchronized (caseActionSerializationLock) {
570  if (null == currentCase) {
571  return;
572  }
573  CaseMetadata metadata = currentCase.getMetadata();
575  deleteCase(metadata);
576  }
577  }
578 
590  @Messages({
591  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
592  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
593  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
594  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
595  })
596  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
597  synchronized (caseActionSerializationLock) {
598  if (null != currentCase) {
599  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
600  }
601  }
602 
603  /*
604  * Set up either a GUI progress indicator without a cancel button (can't
605  * cancel deleting a case) or a logging progress indicator.
606  */
607  ProgressIndicator progressIndicator;
609  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
610  } else {
611  progressIndicator = new LoggingProgressIndicator();
612  }
613  progressIndicator.start(Bundle.Case_progressMessage_preparing());
614  try {
615  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
616  deleteCase(metadata, progressIndicator);
617  } else {
618  /*
619  * First, acquire an exclusive case directory lock. The case
620  * cannot be deleted if another node has it open.
621  */
622  progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
624  assert (null != dirLock);
625  deleteCase(metadata, progressIndicator);
626  } catch (CoordinationServiceException ex) {
627  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
628  }
629  }
630  } finally {
631  progressIndicator.finish();
632  }
633  }
634 
645  @Messages({
646  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
647  })
648  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
649  synchronized (caseActionSerializationLock) {
650  if (null != currentCase) {
651  try {
653  } catch (CaseActionException ex) {
654  /*
655  * Notify the user and continue (the error has already been
656  * logged in closeCurrentCase.
657  */
658  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
659  }
660  }
661  try {
662  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
663  newCurrentCase.open(isNewCase);
664  currentCase = newCurrentCase;
665  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
667  updateGUIForCaseOpened(newCurrentCase);
668  }
669  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
670  } catch (CaseActionCancelledException ex) {
671  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
672  throw ex;
673  } catch (CaseActionException ex) {
674  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
675  throw ex;
676  }
677  }
678  }
679 
688  private static String displayNameToUniqueName(String caseDisplayName) {
689  /*
690  * Replace all non-ASCII characters.
691  */
692  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
693 
694  /*
695  * Replace all control characters.
696  */
697  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
698 
699  /*
700  * Replace /, \, :, ?, space, ' ".
701  */
702  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
703 
704  /*
705  * Make it all lowercase.
706  */
707  uniqueCaseName = uniqueCaseName.toLowerCase();
708 
709  /*
710  * Add a time stamp for uniqueness.
711  */
712  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
713  Date date = new Date();
714  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
715 
716  return uniqueCaseName;
717  }
718 
727  static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
728 
729  File caseDirF = new File(caseDir);
730 
731  if (caseDirF.exists()) {
732  if (caseDirF.isFile()) {
733  throw new CaseActionException(
734  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
735 
736  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
737  throw new CaseActionException(
738  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
739  }
740  }
741 
742  try {
743  boolean result = (caseDirF).mkdirs(); // create root case Directory
744 
745  if (result == false) {
746  throw new CaseActionException(
747  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
748  }
749 
750  // create the folders inside the case directory
751  String hostClause = "";
752 
753  if (caseType == CaseType.MULTI_USER_CASE) {
754  hostClause = File.separator + NetworkUtils.getLocalHostName();
755  }
756  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
757  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
758  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
759  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
760 
761  if (result == false) {
762  throw new CaseActionException(
763  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
764  }
765 
766  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
767  result = new File(modulesOutDir).mkdir();
768 
769  if (result == false) {
770  throw new CaseActionException(
771  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
772  modulesOutDir));
773  }
774 
775  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
776  result = new File(reportsOutDir).mkdir();
777 
778  if (result == false) {
779  throw new CaseActionException(
780  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
781  modulesOutDir));
782 
783  }
784 
785  } catch (MissingResourceException | CaseActionException e) {
786  throw new CaseActionException(
787  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
788  }
789  }
790 
798  static Map<Long, String> getImagePaths(SleuthkitCase db) {
799  Map<Long, String> imgPaths = new HashMap<>();
800  try {
801  Map<Long, List<String>> imgPathsList = db.getImagePaths();
802  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
803  if (entry.getValue().size() > 0) {
804  imgPaths.put(entry.getKey(), entry.getValue().get(0));
805  }
806  }
807  } catch (TskCoreException ex) {
808  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
809  }
810  return imgPaths;
811  }
812 
829  @Messages({
830  "Case.progressMessage.deletingTextIndex=Deleting text index...",
831  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
832  "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
833  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
834  })
835  private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
836  boolean errorsOccurred = false;
837  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
838  /*
839  * Delete the case database from the database server.
840  */
841  try {
842  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
843  CaseDbConnectionInfo db;
845  Class.forName("org.postgresql.Driver"); //NON-NLS
846  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
847  Statement statement = connection.createStatement();) {
848  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
849  statement.execute(deleteCommand);
850  }
851  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
852  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);
853  errorsOccurred = true;
854  }
855  }
856 
857  /*
858  * Delete the text index.
859  */
860  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
861  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
862  try {
863  searchService.deleteTextIndex(metadata);
864  } catch (KeywordSearchServiceException ex) {
865  logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
866  errorsOccurred = true;
867  }
868  }
869 
870  /*
871  * Delete the case directory.
872  */
873  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
874  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
875  logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
876  errorsOccurred = true;
877  }
878 
879  /*
880  * If running in a GUI, remove the case from the Recent Cases menu
881  */
883  SwingUtilities.invokeLater(() -> {
884  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
885  });
886  }
887 
888  if (errorsOccurred) {
889  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
890  }
891  }
892 
903  @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
905  try {
906  String resourcesNodeName = caseDir + "_resources";
907  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
908  if (null == lock) {
909  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
910  }
911  return lock;
912  } catch (InterruptedException ex) {
913  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
914  } catch (CoordinationServiceException ex) {
915  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
916  }
917  }
918 
922  private static void updateGUIForCaseOpened(Case newCurrentCase) {
924  SwingUtilities.invokeLater(() -> {
925  /*
926  * If the case database was upgraded for a new schema and a
927  * backup database was created, notify the user.
928  */
929  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
930  String backupDbPath = caseDb.getBackupDatabasePath();
931  if (null != backupDbPath) {
932  JOptionPane.showMessageDialog(
933  mainFrame,
934  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
935  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
936  JOptionPane.INFORMATION_MESSAGE);
937  }
938 
939  /*
940  * Look for the files for the data sources listed in the case
941  * database and give the user the opportunity to locate any that
942  * are missing.
943  */
944  Map<Long, String> imgPaths = getImagePaths(caseDb);
945  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
946  long obj_id = entry.getKey();
947  String path = entry.getValue();
948  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
949  if (!fileExists) {
950  int response = JOptionPane.showConfirmDialog(
951  mainFrame,
952  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
953  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
954  JOptionPane.YES_NO_OPTION);
955  if (response == JOptionPane.YES_OPTION) {
956  MissingImageDialog.makeDialog(obj_id, caseDb);
957  } else {
958  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
959 
960  }
961  }
962  }
963 
964  /*
965  * Enable the case-specific actions.
966  */
967  CallableSystemAction.get(AddImageAction.class
968  ).setEnabled(true);
969  CallableSystemAction
970  .get(CaseCloseAction.class
971  ).setEnabled(true);
972  CallableSystemAction
973  .get(CasePropertiesAction.class
974  ).setEnabled(true);
975  CallableSystemAction
976  .get(CaseDeleteAction.class
977  ).setEnabled(true);
978  CallableSystemAction
979  .get(OpenTimelineAction.class
980  ).setEnabled(true);
981  CallableSystemAction
982  .get(OpenOutputFolderAction.class
983  ).setEnabled(false);
984 
985  /*
986  * Add the case to the recent cases tracker that supplies a list
987  * of recent cases to the recent cases menu item and the
988  * open/create case dialog.
989  */
990  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
991 
992  /*
993  * Open the top components (windows within the main application
994  * window).
995  */
996  if (newCurrentCase.hasData()) {
998  }
999 
1000  /*
1001  * Reset the main window title to:
1002  *
1003  * [curent case display name] - [application name].
1004  */
1005  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + UserPreferences.getAppName());
1006  });
1007  }
1008  }
1009 
1010  /*
1011  * Update the GUI to to reflect the lack of a current case.
1012  */
1013  private static void updateGUIForCaseClosed() {
1015  SwingUtilities.invokeLater(() -> {
1016  /*
1017  * Close the top components (windows within the main application
1018  * window).
1019  */
1021 
1022  /*
1023  * Disable the case-specific menu items.
1024  */
1025  CallableSystemAction
1026  .get(AddImageAction.class
1027  ).setEnabled(false);
1028  CallableSystemAction
1029  .get(CaseCloseAction.class
1030  ).setEnabled(false);
1031  CallableSystemAction
1032  .get(CasePropertiesAction.class
1033  ).setEnabled(false);
1034  CallableSystemAction
1035  .get(CaseDeleteAction.class
1036  ).setEnabled(false);
1037  CallableSystemAction
1038  .get(OpenTimelineAction.class
1039  ).setEnabled(false);
1040  CallableSystemAction
1041  .get(OpenOutputFolderAction.class
1042  ).setEnabled(false);
1043 
1044  /*
1045  * Clear the notifications in the notfier component in the lower
1046  * right hand corner of the main application window.
1047  */
1049 
1050  /*
1051  * Reset the main window title to be just the application name,
1052  * instead of [curent case display name] - [application name].
1053  */
1054  mainFrame.setTitle(UserPreferences.getAppName());
1055  });
1056  }
1057  }
1058 
1062  private static void clearTempSubDir(String tempSubDirPath) {
1063  File tempFolder = new File(tempSubDirPath);
1064  if (tempFolder.isDirectory()) {
1065  File[] files = tempFolder.listFiles();
1066  if (files.length > 0) {
1067  for (File file : files) {
1068  if (file.isDirectory()) {
1069  FileUtil.deleteDir(file);
1070  } else {
1071  file.delete();
1072  }
1073  }
1074  }
1075  }
1076  }
1077 
1083  public SleuthkitCase getSleuthkitCase() {
1084  return this.caseDb;
1085  }
1086 
1093  return caseServices;
1094  }
1095 
1102  return metadata.getCaseType();
1103  }
1104 
1110  public String getCreatedDate() {
1111  return metadata.getCreatedDate();
1112  }
1113 
1119  public String getName() {
1120  return metadata.getCaseName();
1121  }
1122 
1128  public String getDisplayName() {
1129  return metadata.getCaseDisplayName();
1130  }
1131 
1137  public String getNumber() {
1138  return metadata.getCaseNumber();
1139  }
1140 
1146  public String getExaminer() {
1147  return metadata.getExaminer();
1148  }
1149 
1155  public String getCaseDirectory() {
1156  return metadata.getCaseDirectory();
1157  }
1158 
1167  public String getOutputDirectory() {
1168  String caseDirectory = getCaseDirectory();
1169  Path hostPath;
1170  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1171  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1172  } else {
1173  hostPath = Paths.get(caseDirectory);
1174  }
1175  if (!hostPath.toFile().exists()) {
1176  hostPath.toFile().mkdirs();
1177  }
1178  return hostPath.toString();
1179  }
1180 
1187  public String getTempDirectory() {
1188  return getOrCreateSubdirectory(TEMP_FOLDER);
1189  }
1190 
1197  public String getCacheDirectory() {
1198  return getOrCreateSubdirectory(CACHE_FOLDER);
1199  }
1200 
1207  public String getExportDirectory() {
1208  return getOrCreateSubdirectory(EXPORT_FOLDER);
1209  }
1210 
1217  public String getLogDirectoryPath() {
1218  return getOrCreateSubdirectory(LOG_FOLDER);
1219  }
1220 
1227  public String getReportDirectory() {
1228  return getOrCreateSubdirectory(REPORTS_FOLDER);
1229  }
1230 
1237  public String getModuleDirectory() {
1238  return getOrCreateSubdirectory(MODULE_FOLDER);
1239  }
1240 
1249  Path path = Paths.get(getModuleDirectory());
1251  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1252  } else {
1253  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1254  }
1255  }
1256 
1266  public List<Content> getDataSources() throws TskCoreException {
1267  List<Content> list = caseDb.getRootObjects();
1268  hasDataSources = (list.size() > 0);
1269  return list;
1270  }
1271 
1277  public Set<TimeZone> getTimeZones() {
1278  Set<TimeZone> timezones = new HashSet<>();
1279  try {
1280  for (Content c : getDataSources()) {
1281  final Content dataSource = c.getDataSource();
1282  if ((dataSource != null) && (dataSource instanceof Image)) {
1283  Image image = (Image) dataSource;
1284  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1285  }
1286  }
1287  } catch (TskCoreException ex) {
1288  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1289  }
1290  return timezones;
1291  }
1292 
1299  public String getTextIndexName() {
1300  return getMetadata().getTextIndexName();
1301  }
1302 
1309  public boolean hasData() {
1310  if (!hasDataSources) {
1311  try {
1312  hasDataSources = (getDataSources().size() > 0);
1313  } catch (TskCoreException ex) {
1314  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1315  }
1316  }
1317  return hasDataSources;
1318  }
1319 
1330  public void notifyAddingDataSource(UUID eventId) {
1331  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1332  }
1333 
1344  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1345  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1346  }
1347 
1359  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1360  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1361  }
1362 
1370  public void notifyContentTagAdded(ContentTag newTag) {
1371  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1372  }
1373 
1381  public void notifyContentTagDeleted(ContentTag deletedTag) {
1382  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1383  }
1384 
1392  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1393  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1394  }
1395 
1403  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1404  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1405  }
1406 
1418  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1419  String normalizedLocalPath;
1420  try {
1421  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1422  } catch (InvalidPathException ex) {
1423  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1424  throw new TskCoreException(errorMsg, ex);
1425  }
1426  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName);
1427  eventPublisher.publish(new ReportAddedEvent(report));
1428  }
1429 
1438  public List<Report> getAllReports() throws TskCoreException {
1439  return this.caseDb.getAllReports();
1440  }
1441 
1450  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1451  for (Report report : reports) {
1452  this.caseDb.deleteReport(report);
1453  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1454  }
1455  }
1456 
1462  CaseMetadata getMetadata() {
1463  return metadata;
1464  }
1465 
1469  @Messages({
1470  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata, cannot change case display name."
1471  })
1472  void updateDisplayName(String newDisplayName) throws CaseActionException {
1473  String oldDisplayName = metadata.getCaseDisplayName();
1474  try {
1475  metadata.setCaseDisplayName(newDisplayName);
1476  } catch (CaseMetadataException ex) {
1477  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError());
1478  }
1479  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldDisplayName, newDisplayName));
1480  if (RuntimeProperties.runningWithGUI()) {
1481  SwingUtilities.invokeLater(() -> {
1482  mainFrame.setTitle(newDisplayName + " - " + UserPreferences.getAppName());
1483  try {
1484  RecentCases.getInstance().updateRecentCase(oldDisplayName, metadata.getFilePath().toString(), newDisplayName, metadata.getFilePath().toString());
1485  } catch (Exception ex) {
1486  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1487  }
1488  });
1489  }
1490  }
1491 
1506  private Case(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner) {
1507  metadata = new CaseMetadata(caseDir, caseType, displayNameToUniqueName(caseDisplayName), caseDisplayName, caseNumber, examiner);
1508  }
1509 
1515  private Case(CaseMetadata caseMetaData) {
1516  metadata = caseMetaData;
1517  }
1518 
1534  @Messages({
1535  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1536  "Case.progressIndicatorTitle.openingCase=Opening Case",
1537  "Case.progressIndicatorCancelButton.label=Cancel",
1538  "Case.progressMessage.preparing=Preparing...",
1539  "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1540  "Case.progressMessage.cancelling=Cancelling...",
1541  "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1542  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1543  })
1544  private void open(boolean isNewCase) throws CaseActionException {
1545  /*
1546  * Create and start either a GUI progress indicator with a Cancel button
1547  * or a logging progress indicator.
1548  */
1549  CancelButtonListener cancelButtonListener = null;
1550  ProgressIndicator progressIndicator;
1552  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1553  String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1554  progressIndicator = new ModalDialogProgressIndicator(
1555  mainFrame,
1556  progressIndicatorTitle,
1557  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1558  Bundle.Case_progressIndicatorCancelButton_label(),
1559  cancelButtonListener);
1560  } else {
1561  progressIndicator = new LoggingProgressIndicator();
1562  }
1563  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1564 
1565  /*
1566  * Creating/opening a case is always done by creating a task running in
1567  * the same non-UI thread that will be used to close the case, so a
1568  * single-threaded executor service is created here and saved as case
1569  * state (must be volatile for cancellation to work).
1570  *
1571  * --- If the case is a single-user case, this supports cancelling
1572  * opening of the case by cancelling the task.
1573  *
1574  * --- If the case is a multi-user case, this still supports
1575  * cancellation, but it also makes it possible for the shared case
1576  * directory lock held as long as the case is open to be released in the
1577  * same thread in which it was acquired, as is required by the
1578  * coordination service.
1579  */
1580  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1581  caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1582  Future<Void> future = caseLockingExecutor.submit(() -> {
1583  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1584  open(isNewCase, progressIndicator);
1585  } else {
1586  /*
1587  * First, acquire a shared case directory lock that will be held
1588  * as long as this node has this case open. This will prevent
1589  * deletion of the case by another node. Next, acquire an
1590  * exclusive case resources lock to ensure only one node at a
1591  * time can create/open/upgrade/close the case resources.
1592  */
1593  progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1596  assert (null != resourcesLock);
1597  open(isNewCase, progressIndicator);
1598  } catch (CaseActionException ex) {
1599  releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
1600  throw ex;
1601  }
1602  }
1603  return null;
1604  });
1605  if (null != cancelButtonListener) {
1606  cancelButtonListener.setCaseActionFuture(future);
1607  }
1608 
1609  /*
1610  * Wait for the case creation/opening task to finish.
1611  */
1612  try {
1613  future.get();
1614  } catch (InterruptedException discarded) {
1615  /*
1616  * The thread this method is running in has been interrupted. Cancel
1617  * the create/open task, wait for it to finish, and shut down the
1618  * executor. This can be done safely because if the task is
1619  * completed with a cancellation condition, the case will have been
1620  * closed and the case directory lock released will have been
1621  * released.
1622  */
1623  if (null != cancelButtonListener) {
1624  cancelButtonListener.actionPerformed(null);
1625  } else {
1626  future.cancel(true);
1627  }
1628  Case.shutDownTaskExecutor(caseLockingExecutor);
1629  } catch (CancellationException discarded) {
1630  /*
1631  * The create/open task has been cancelled. Wait for it to finish,
1632  * and shut down the executor. This can be done safely because if
1633  * the task is completed with a cancellation condition, the case
1634  * will have been closed and the case directory lock released will
1635  * have been released.
1636  */
1637  Case.shutDownTaskExecutor(caseLockingExecutor);
1638  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1639  } catch (ExecutionException ex) {
1640  /*
1641  * The create/open task has thrown an exception. Wait for it to
1642  * finish, and shut down the executor. This can be done safely
1643  * because if the task is completed with an execution condition, the
1644  * case will have been closed and the case directory lock released
1645  * will have been released.
1646  */
1647  Case.shutDownTaskExecutor(caseLockingExecutor);
1648  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1649  } finally {
1650  progressIndicator.finish();
1651  }
1652  }
1653 
1665  private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
1666  try {
1667  if (Thread.currentThread().isInterrupted()) {
1668  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1669  }
1670 
1671  if (isNewCase) {
1672  createCaseData(progressIndicator);
1673  } else {
1674  openCaseData(progressIndicator);
1675  }
1676 
1677  if (Thread.currentThread().isInterrupted()) {
1678  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1679  }
1680 
1681  openServices(progressIndicator);
1682 
1683  if (Thread.currentThread().isInterrupted()) {
1684  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1685  }
1686  } catch (CaseActionException ex) {
1687  /*
1688  * Cancellation or failure. Clean up. The sleep is a little hack to
1689  * clear the interrupted flag for this thread if this is a
1690  * cancellation scenario, so that the clean up can run to completion
1691  * in this thread.
1692  */
1693  try {
1694  Thread.sleep(1);
1695  } catch (InterruptedException discarded) {
1696  }
1697  close(progressIndicator);
1698  throw ex;
1699  }
1700  }
1701 
1712  @Messages({
1713  "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1714  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1715  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}",
1716  "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
1717  })
1718  private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1719  /*
1720  * Create the case directory, if it does not already exist.
1721  *
1722  * TODO (JIRA-2180): Always create the case directory as part of the
1723  * case creation process.
1724  */
1725  if (new File(metadata.getCaseDirectory()).exists() == false) {
1726  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1727  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
1728  }
1729 
1730  /*
1731  * Create the case database.
1732  */
1733  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1734  try {
1735  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1736  /*
1737  * For single-user cases, the case database is a SQLite database
1738  * with a standard name, physically located in the root of the
1739  * case directory.
1740  */
1741  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
1742  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
1743  } else {
1744  /*
1745  * For multi-user cases, the case database is a PostgreSQL
1746  * database with a name derived from the case display name,
1747  * physically located on a database server.
1748  */
1749  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1750  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
1751  }
1752  } catch (TskCoreException ex) {
1753  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
1754  } catch (UserPreferencesException ex) {
1755  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1756  } catch (CaseMetadataException ex) {
1757  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1758  }
1759  }
1760 
1771  @Messages({
1772  "Case.progressMessage.openingCaseDatabase=Opening case database...",
1773  "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database."
1774  })
1775  private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1776  try {
1777  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
1778  String databaseName = metadata.getCaseDatabaseName();
1779  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1780  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
1782  try {
1783  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1784 
1785  } catch (UserPreferencesException ex) {
1786  throw new CaseActionException(NbBundle.getMessage(Case.class,
1787  "Case.databaseConnectionInfo.error.msg"), ex);
1788 
1789  }
1790  } else {
1791  throw new CaseActionException(NbBundle.getMessage(Case.class,
1792  "Case.open.exception.multiUserCaseNotEnabled"));
1793  }
1794  } catch (TskCoreException ex) {
1795  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
1796  }
1797  }
1798 
1807  @Messages({
1808  "Case.progressMessage.switchingLogDirectory=Switching log directory...",
1809  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
1810  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
1811  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
1812  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
1813  private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
1814  /*
1815  * Switch to writing to the application logs in the logs subdirectory of
1816  * the case directory.
1817  */
1818  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
1820  if (Thread.currentThread().isInterrupted()) {
1821  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1822  }
1823 
1824  /*
1825  * Clear the temp subdirectory of the case directory.
1826  */
1827  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
1829  if (Thread.currentThread().isInterrupted()) {
1830  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1831  }
1832 
1833  /*
1834  * Open the case-level services.
1835  */
1836  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
1837  this.caseServices = new Services(caseDb);
1838  if (Thread.currentThread().isInterrupted()) {
1839  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1840  }
1841 
1842  /*
1843  * Allow any registered application services to open any resources
1844  * specific to this case.
1845  */
1846  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
1848  if (Thread.currentThread().isInterrupted()) {
1849  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1850  }
1851 
1852  /*
1853  * If this case is a multi-user case, set up for communication with
1854  * other nodes.
1855  */
1856  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1857  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
1858  try {
1859  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
1860  if (Thread.currentThread().isInterrupted()) {
1861  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1862  }
1863  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
1864  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
1865  /*
1866  * The collaboration monitor and event channel are not
1867  * essential. Log an error and notify the user, but do not
1868  * throw.
1869  */
1870  logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
1872  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
1873  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"),
1874  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
1875  }
1876  }
1877  }
1878  }
1879 
1884  @NbBundle.Messages({
1885  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
1886  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
1887  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
1888  })
1890  /*
1891  * Each service gets its own independently cancellable/interruptible
1892  * task, running in a named thread managed by an executor service, with
1893  * its own progress indicator. This allows for cancellation of the
1894  * opening of case resources for individual services. It also makes it
1895  * possible to ensure that each service task completes before the next
1896  * one starts by awaiting termination of the executor service.
1897  */
1898  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
1899  /*
1900  * Create a progress indicator for the task and start the task. If
1901  * running with a GUI, the progress indicator will be a dialog box
1902  * with a Cancel button.
1903  */
1904  CancelButtonListener cancelButtonListener = null;
1905  ProgressIndicator progressIndicator;
1907  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
1908  progressIndicator = new ModalDialogProgressIndicator(
1909  mainFrame,
1910  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
1911  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1912  Bundle.Case_progressIndicatorCancelButton_label(),
1913  cancelButtonListener);
1914  } else {
1915  progressIndicator = new LoggingProgressIndicator();
1916  }
1917  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1918  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
1919  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
1920  threadNameSuffix = threadNameSuffix.toLowerCase();
1921  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
1922  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
1923  Future<Void> future = executor.submit(() -> {
1924  service.openCaseResources(context);
1925  return null;
1926  });
1927  if (null != cancelButtonListener) {
1928  cancelButtonListener.setCaseContext(context);
1929  cancelButtonListener.setCaseActionFuture(future);
1930  }
1931 
1932  /*
1933  * Wait for the task to either be completed or
1934  * cancelled/interrupted, or for the opening of the case to be
1935  * cancelled.
1936  */
1937  try {
1938  future.get();
1939  } catch (InterruptedException discarded) {
1940  /*
1941  * The parent create/open case task has been cancelled.
1942  */
1943  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()));
1944  future.cancel(true);
1945  } catch (CancellationException discarded) {
1946  /*
1947  * The opening of case resources by the application service has
1948  * been cancelled, so the executor service has thrown. Note that
1949  * there is no guarantee the task itself has responded to the
1950  * cancellation request yet.
1951  */
1952  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()));
1953  } catch (ExecutionException ex) {
1954  /*
1955  * An exception was thrown while executing the task. The
1956  * case-specific application service resources are not
1957  * essential. Log an error and notify the user if running the
1958  * desktop GUI, but do not throw.
1959  */
1960  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
1962  SwingUtilities.invokeLater(() -> {
1963  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
1964  });
1965  }
1966  } finally {
1967  /*
1968  * Shut down the executor service and wait for it to finish.
1969  * This ensures that the task has finished. Without this, it
1970  * would be possible to start the next task before the current
1971  * task responded to a cancellation request.
1972  */
1973  shutDownTaskExecutor(executor);
1974  progressIndicator.finish();
1975  }
1976 
1977  if (Thread.currentThread().isInterrupted()) {
1978  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1979  }
1980  }
1981  }
1982 
1986  private void close() throws CaseActionException {
1987  /*
1988  * Set up either a GUI progress indicator without a Cancel button or a
1989  * logging progress indicator.
1990  */
1991  ProgressIndicator progressIndicator;
1993  progressIndicator = new ModalDialogProgressIndicator(
1994  mainFrame,
1995  Bundle.Case_progressIndicatorTitle_closingCase());
1996  } else {
1997  progressIndicator = new LoggingProgressIndicator();
1998  }
1999  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2000 
2001  /*
2002  * Closing a case is always done in the same non-UI thread that
2003  * opened/created the case. If the case is a multi-user case, this
2004  * ensures that case directory lock that is held as long as the case is
2005  * open is released in the same thread in which it was acquired, as is
2006  * required by the coordination service.
2007  */
2008  Future<Void> future = caseLockingExecutor.submit(() -> {
2009  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2010  close(progressIndicator);
2011  } else {
2012  /*
2013  * Acquire an exclusive case resources lock to ensure only one
2014  * node at a time can create/open/upgrade/close the case
2015  * resources.
2016  */
2017  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2019  assert (null != resourcesLock);
2020  close(progressIndicator);
2021  } finally {
2022  /*
2023  * Always release the case directory lock that was acquired
2024  * when the case was opened.
2025  */
2027  }
2028  }
2029  return null;
2030  });
2031 
2032  try {
2033  future.get();
2034  } catch (InterruptedException | CancellationException unused) {
2035  /*
2036  * The wait has been interrupted by interrupting the thread running
2037  * this method. Not allowing cancellation of case closing, so ignore
2038  * the interrupt. Likewsie, cancellation of the case closing task is
2039  * not supported.
2040  */
2041  } catch (ExecutionException ex) {
2042  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2043  } finally {
2044  shutDownTaskExecutor(caseLockingExecutor);
2045  progressIndicator.finish();
2046  }
2047  }
2048 
2054  @Messages({
2055  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2056  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2057  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2058  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2059  })
2060  private void close(ProgressIndicator progressIndicator) {
2062 
2063  /*
2064  * Stop sending/receiving case events to and from other nodes if this is
2065  * a multi-user case.
2066  */
2067  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2068  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2069  if (null != collaborationMonitor) {
2070  collaborationMonitor.shutdown();
2071  }
2072  eventPublisher.closeRemoteEventChannel();
2073  }
2074 
2075  /*
2076  * Allow all registered application services providers to close
2077  * resources related to the case.
2078  */
2079  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2081 
2082  /*
2083  * Close the case-level services.
2084  */
2085  if (null != caseServices) {
2086  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2087  try {
2088  this.caseServices.close();
2089  } catch (IOException ex) {
2090  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2091  }
2092  }
2093 
2094  /*
2095  * Close the case database
2096  */
2097  if (null != caseDb) {
2098  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2099  caseDb.close();
2100  }
2101 
2102  /*
2103  * Switch the log directory.
2104  */
2105  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2107  }
2108 
2113  @Messages({
2114  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2115  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2116  })
2118  /*
2119  * Each service gets its own independently cancellable task, and thus
2120  * its own task progress indicator.
2121  */
2122  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2123  ProgressIndicator progressIndicator;
2125  progressIndicator = new ModalDialogProgressIndicator(
2126  mainFrame,
2127  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2128  } else {
2129  progressIndicator = new LoggingProgressIndicator();
2130  }
2131  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2132  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2133  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2134  threadNameSuffix = threadNameSuffix.toLowerCase();
2135  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2136  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2137  Future<Void> future = executor.submit(() -> {
2138  service.closeCaseResources(context);
2139  return null;
2140  });
2141  try {
2142  future.get();
2143  } catch (InterruptedException ex) {
2144  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2145  } catch (CancellationException ex) {
2146  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2147  } catch (ExecutionException ex) {
2148  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2150  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2151  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2152  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2153  }
2154  } finally {
2155  shutDownTaskExecutor(executor);
2156  progressIndicator.finish();
2157  }
2158  }
2159  }
2160 
2169  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2170  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2171  try {
2172  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2173  if (null == caseDirLock) {
2174  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2175  }
2176  } catch (InterruptedException | CoordinationServiceException ex) {
2177  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2178  }
2179  }
2180 
2186  private void releaseSharedCaseDirLock(String caseDir) {
2187  if (caseDirLock != null) {
2188  try {
2189  caseDirLock.release();
2190  caseDirLock = null;
2192  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2193  }
2194  }
2195  }
2196 
2203  private String getOrCreateSubdirectory(String subDirectoryName) {
2204  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2205  if (!subDirectory.exists()) {
2206  subDirectory.mkdirs();
2207  }
2208  return subDirectory.toString();
2209 
2210  }
2211 
2220  private static void shutDownTaskExecutor(ExecutorService executor) {
2221  executor.shutdown();
2222  boolean taskCompleted = false;
2223  while (!taskCompleted) {
2224  try {
2225  taskCompleted = executor.awaitTermination(EXECUTOR_AWAIT_TIMEOUT_SECS, TimeUnit.SECONDS);
2226  } catch (InterruptedException ignored) {
2227  /*
2228  * The current policy is to wait for the task to finish so that
2229  * the case can be left in a consistent state.
2230  *
2231  * For a specific example of the motivation for this policy,
2232  * note that a application service (Solr search service)
2233  * experienced an error condition when opening case resources
2234  * that left the service blocked uninterruptibly on a socket
2235  * read. This eventually led to a mysterious "freeze" as the
2236  * user-cancelled service task continued to run holdiong a lock
2237  * that a UI thread soon tried to acquire. Thus it has been
2238  * deemed better to make the "freeze" happen in a more
2239  * informative way, i.e., with the progress indicator for the
2240  * unfinished task on the screen, if a similar error condition
2241  * arises again.
2242  */
2243  }
2244  }
2245  }
2246 
2251  @ThreadSafe
2252  private final static class CancelButtonListener implements ActionListener {
2253 
2254  private final String cancellationMessage;
2255  @GuardedBy("this")
2256  private boolean cancelRequested;
2257  @GuardedBy("this")
2259  @GuardedBy("this")
2260  private Future<?> caseActionFuture;
2261 
2270  private CancelButtonListener(String cancellationMessage) {
2271  this.cancellationMessage = cancellationMessage;
2272  }
2273 
2279  private synchronized void setCaseContext(CaseContext caseContext) {
2280  this.caseContext = caseContext;
2281  /*
2282  * If the cancel button has already been pressed, pass the
2283  * cancellation on to the case context.
2284  */
2285  if (cancelRequested) {
2286  cancel();
2287  }
2288  }
2289 
2295  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
2296  this.caseActionFuture = caseActionFuture;
2297  /*
2298  * If the cancel button has already been pressed, cancel the Future
2299  * of the task.
2300  */
2301  if (cancelRequested) {
2302  cancel();
2303  }
2304  }
2305 
2311  @Override
2312  public synchronized void actionPerformed(ActionEvent event) {
2313  cancel();
2314  }
2315 
2319  private void cancel() {
2320  /*
2321  * At a minimum, set the cancellation requested flag of this
2322  * listener.
2323  */
2324  this.cancelRequested = true;
2325  if (null != this.caseContext) {
2326  /*
2327  * Set the cancellation request flag and display the
2328  * cancellation message in the progress indicator for the case
2329  * context associated with this listener.
2330  */
2332  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
2333  if (progressIndicator instanceof ModalDialogProgressIndicator) {
2334  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2335  }
2336  }
2337  this.caseContext.requestCancel();
2338  }
2339  if (null != this.caseActionFuture) {
2340  /*
2341  * Cancel the Future of the task associated with this listener.
2342  * Note that the task thread will be interrupted if the task is
2343  * blocked.
2344  */
2345  this.caseActionFuture.cancel(true);
2346  }
2347  }
2348  }
2349 
2353  private static class TaskThreadFactory implements ThreadFactory {
2354 
2355  private final String threadName;
2356 
2357  private TaskThreadFactory(String threadName) {
2358  this.threadName = threadName;
2359  }
2360 
2361  @Override
2362  public Thread newThread(Runnable task) {
2363  return new Thread(task, threadName);
2364  }
2365 
2366  }
2367 
2375  @Deprecated
2376  public static String getAppName() {
2377  return UserPreferences.getAppName();
2378  }
2379 
2399  @Deprecated
2400  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2401  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2402  }
2403 
2424  @Deprecated
2425  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2426  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2427  }
2428 
2440  @Deprecated
2441  public static void open(String caseMetadataFilePath) throws CaseActionException {
2442  openAsCurrentCase(caseMetadataFilePath);
2443  }
2444 
2454  @Deprecated
2455  public void closeCase() throws CaseActionException {
2456  closeCurrentCase();
2457  }
2458 
2464  @Deprecated
2465  public static void invokeStartupDialog() {
2467  }
2468 
2482  @Deprecated
2483  public static String convertTimeZone(String timeZoneId) {
2484  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2485  }
2486 
2496  @Deprecated
2497  public static boolean pathExists(String filePath) {
2498  return new File(filePath).isFile();
2499  }
2500 
2509  @Deprecated
2510  public static String getAutopsyVersion() {
2511  return Version.getVersion();
2512  }
2513 
2521  @Deprecated
2522  public static boolean existsCurrentCase() {
2523  return isCaseOpen();
2524  }
2525 
2535  @Deprecated
2536  public static String getModulesOutputDirRelPath() {
2537  return "ModuleOutput"; //NON-NLS
2538  }
2539 
2549  @Deprecated
2550  public static PropertyChangeSupport
2552  return new PropertyChangeSupport(Case.class
2553  );
2554  }
2555 
2564  @Deprecated
2565  public String getModulesOutputDirAbsPath() {
2566  return getModuleDirectory();
2567  }
2568 
2583  @Deprecated
2584  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
2585  try {
2586  Image newDataSource = caseDb.getImageById(imgId);
2587  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
2588  return newDataSource;
2589  } catch (TskCoreException ex) {
2590  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
2591  }
2592  }
2593 
2601  @Deprecated
2602  public Set<TimeZone> getTimeZone() {
2603  return getTimeZones();
2604  }
2605 
2616  @Deprecated
2617  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
2618  deleteReports(reports);
2619  }
2620 
2621 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:134
List< Content > getDataSources()
Definition: Case.java:1266
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1381
Case(CaseMetadata caseMetaData)
Definition: Case.java:1515
static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS
Definition: Case.java:128
static CaseType fromString(String typeName)
Definition: Case.java:177
static void shutDownTaskExecutor(ExecutorService executor)
Definition: Case.java:2220
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:131
Case(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:1506
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1403
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:2584
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:904
static boolean existsCurrentCase()
Definition: Case.java:2522
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:369
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:133
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:120
static final String EXPORT_FOLDER
Definition: Case.java:124
static volatile Frame mainFrame
Definition: Case.java:136
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2483
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 final String CACHE_FOLDER
Definition: Case.java:123
void createCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1718
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1418
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:922
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2425
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:121
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:596
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:2617
volatile ExecutorService caseLockingExecutor
Definition: Case.java:139
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:2279
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1062
static boolean isValidName(String caseName)
Definition: Case.java:423
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2170
CollaborationMonitor collaborationMonitor
Definition: Case.java:142
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2186
static String getModulesOutputDirRelPath()
Definition: Case.java:2536
void openCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1775
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:2312
static final String MODULE_FOLDER
Definition: Case.java:129
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2400
synchronized void openRemoteEventChannel(String channelName)
void openServices(ProgressIndicator progressIndicator)
Definition: Case.java:1813
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:688
void open(boolean isNewCase)
Definition: Case.java:1544
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:481
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:411
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:119
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2060
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1392
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2551
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:357
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2295
static final long EXECUTOR_AWAIT_TIMEOUT_SECS
Definition: Case.java:130
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:401
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1450
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1359
static boolean pathExists(String filePath)
Definition: Case.java:2497
static void open(String caseMetadataFilePath)
Definition: Case.java:2441
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:648
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2203
boolean equalsName(String otherTypeName)
Definition: Case.java:235
static final String EVENT_CHANNEL_NAME
Definition: Case.java:122
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:125
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:137
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1330
CoordinationService.Lock caseDirLock
Definition: Case.java:140
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:391
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1370
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:132
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:454
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1344
static final Object caseActionSerializationLock
Definition: Case.java:135
void open(boolean isNewCase, ProgressIndicator progressIndicator)
Definition: Case.java:1665
static final String REPORTS_FOLDER
Definition: Case.java:126
static final String TEMP_FOLDER
Definition: Case.java:127
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:381
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:835

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