Autopsy  4.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.nio.file.InvalidPathException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.sql.Connection;
32 import java.sql.DriverManager;
33 import java.sql.SQLException;
34 import java.sql.Statement;
35 import java.text.SimpleDateFormat;
36 import java.util.Collection;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.MissingResourceException;
43 import java.util.Set;
44 import java.util.TimeZone;
45 import java.util.UUID;
46 import java.util.concurrent.CancellationException;
47 import java.util.concurrent.ExecutionException;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors;
50 import java.util.concurrent.Future;
51 import java.util.concurrent.ThreadFactory;
52 import java.util.concurrent.TimeUnit;
53 import java.util.logging.Level;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56 import javax.annotation.concurrent.GuardedBy;
57 import javax.annotation.concurrent.ThreadSafe;
58 import javax.swing.JOptionPane;
59 import javax.swing.SwingUtilities;
60 import org.openide.util.Lookup;
61 import org.openide.util.NbBundle;
62 import org.openide.util.NbBundle.Messages;
63 import org.openide.util.actions.CallableSystemAction;
64 import org.openide.windows.WindowManager;
68 import static org.sleuthkit.autopsy.casemodule.Bundle.*;
106 import org.sleuthkit.datamodel.BlackboardArtifactTag;
107 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
108 import org.sleuthkit.datamodel.Content;
109 import org.sleuthkit.datamodel.ContentTag;
110 import org.sleuthkit.datamodel.Image;
111 import org.sleuthkit.datamodel.Report;
112 import org.sleuthkit.datamodel.SleuthkitCase;
113 import org.sleuthkit.datamodel.TskCoreException;
114 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
115 
119 public class Case {
120 
121  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
122  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
123  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
124  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
125  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
126  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
127  private static final String LOG_FOLDER = "Log"; //NON-NLS
128  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
129  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
130  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
131  private static final long EXECUTOR_AWAIT_TIMEOUT_SECS = 5;
132  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
133  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
134  private static final Logger logger = Logger.getLogger(Case.class.getName());
136  private static final Object caseActionSerializationLock = new Object();
137  private static volatile Frame mainFrame;
138  private static volatile Case currentCase;
139  private final CaseMetadata metadata;
140  private volatile ExecutorService caseLockingExecutor;
142  private SleuthkitCase caseDb;
143  private CollaborationMonitor collaborationMonitor;
145  private boolean hasDataSources;
146 
147  /*
148  * Get a reference to the main window of the desktop application to use to
149  * parent pop up dialogs and initialize the application name for use in
150  * changing the main window title.
151  */
152  static {
153  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
154  @Override
155  public void run() {
156  mainFrame = WindowManager.getDefault().getMainWindow();
157  }
158  });
159  }
160 
164  public enum CaseType {
165 
166  SINGLE_USER_CASE("Single-user case"), //NON-NLS
167  MULTI_USER_CASE("Multi-user case"); //NON-NLS
168 
169  private final String typeName;
170 
178  public static CaseType fromString(String typeName) {
179  if (typeName != null) {
180  for (CaseType c : CaseType.values()) {
181  if (typeName.equalsIgnoreCase(c.toString())) {
182  return c;
183  }
184  }
185  }
186  return null;
187  }
188 
194  @Override
195  public String toString() {
196  return typeName;
197  }
198 
204  @Messages({
205  "Case_caseType_singleUser=Single-user case",
206  "Case_caseType_multiUser=Multi-user case"
207  })
209  if (fromString(typeName) == SINGLE_USER_CASE) {
210  return Bundle.Case_caseType_singleUser();
211  } else {
212  return Bundle.Case_caseType_multiUser();
213  }
214  }
215 
221  private CaseType(String typeName) {
222  this.typeName = typeName;
223  }
224 
235  @Deprecated
236  public boolean equalsName(String otherTypeName) {
237  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
238  }
239 
240  };
241 
246  public enum Events {
247 
350  };
351 
358  public static void addPropertyChangeListener(PropertyChangeListener listener) {
359  addEventSubscriber(Stream.of(Events.values())
360  .map(Events::toString)
361  .collect(Collectors.toSet()), listener);
362  }
363 
370  public static void removePropertyChangeListener(PropertyChangeListener listener) {
371  removeEventSubscriber(Stream.of(Events.values())
372  .map(Events::toString)
373  .collect(Collectors.toSet()), listener);
374  }
375 
384  @Deprecated
385  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
386  eventPublisher.addSubscriber(eventNames, subscriber);
387  }
388 
395  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
396  eventTypes.forEach((Events event) -> {
397  eventPublisher.addSubscriber(event.toString(), subscriber);
398  });
399  }
400 
409  @Deprecated
410  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
411  eventPublisher.addSubscriber(eventName, subscriber);
412  }
413 
420  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
421  eventPublisher.removeSubscriber(eventName, subscriber);
422  }
423 
430  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
431  eventPublisher.removeSubscriber(eventNames, subscriber);
432  }
433 
440  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
441  eventTypes.forEach((Events event) -> {
442  eventPublisher.removeSubscriber(event.toString(), subscriber);
443  });
444  }
445 
454  public static boolean isValidName(String caseName) {
455  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
456  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
457  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
458  }
459 
481  @Messages({
482  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
483  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
484  })
485  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
486  if (caseDisplayName.isEmpty()) {
487  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
488  }
489  if (caseDir.isEmpty()) {
490  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
491  }
492  openAsCurrentCase(new Case(caseType, caseDir, caseDisplayName, caseNumber, examiner), true);
493  }
494 
508  @Messages({
509  "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
510  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
511  })
512  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
514  try {
515  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
516  } catch (CaseMetadataException ex) {
517  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(), ex);
518  }
520  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
521  }
522  openAsCurrentCase(new Case(metadata), false);
523  }
524 
530  public static boolean isCaseOpen() {
531  return currentCase != null;
532  }
533 
541  public static Case getCurrentCase() {
542  /*
543  * Throwing an unchecked exception is a bad idea here.
544  *
545  * TODO (JIRA-2229): Case.getCurrentCase() method throws unchecked
546  * IllegalStateException; change to throw checked exception or return
547  * null
548  */
549  if (null != currentCase) {
550  return currentCase;
551  } else {
552  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
553  }
554  }
555 
564  @Messages({
565  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
566  "Case.progressIndicatorTitle.closingCase=Closing Case"
567  })
568  public static void closeCurrentCase() throws CaseActionException {
569  synchronized (caseActionSerializationLock) {
570  if (null == currentCase) {
571  return;
572  }
573  Case closedCase = currentCase;
574  try {
575  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
576  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
577  currentCase = null;
578  closedCase.close();
579  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
580  } catch (CaseActionException ex) {
581  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
582  throw ex;
583  } finally {
586  }
587  }
588  }
589  }
590 
599  public static void deleteCurrentCase() throws CaseActionException {
600  synchronized (caseActionSerializationLock) {
601  if (null == currentCase) {
602  return;
603  }
604  CaseMetadata metadata = currentCase.getMetadata();
606  deleteCase(metadata);
607  }
608  }
609 
621  @Messages({
622  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
623  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
624  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
625  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
626  })
627  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
628  synchronized (caseActionSerializationLock) {
629  if (null != currentCase) {
630  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
631  }
632  }
633 
634  /*
635  * Set up either a GUI progress indicator without a cancel button (can't
636  * cancel deleting a case) or a logging progress indicator.
637  */
638  ProgressIndicator progressIndicator;
640  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
641  } else {
642  progressIndicator = new LoggingProgressIndicator();
643  }
644  progressIndicator.start(Bundle.Case_progressMessage_preparing());
645  try {
646  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
647  deleteCase(metadata, progressIndicator);
648  } else {
649  /*
650  * First, acquire an exclusive case directory lock. The case
651  * cannot be deleted if another node has it open.
652  */
653  progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
655  assert (null != dirLock);
656  deleteCase(metadata, progressIndicator);
657  } catch (CoordinationServiceException ex) {
658  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
659  }
660  }
661  } finally {
662  progressIndicator.finish();
663  }
664  }
665 
676  @Messages({
677  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
678  })
679  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
680  synchronized (caseActionSerializationLock) {
681  if (null != currentCase) {
682  try {
684  } catch (CaseActionException ex) {
685  /*
686  * Notify the user and continue (the error has already been
687  * logged in closeCurrentCase.
688  */
689  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
690  }
691  }
692  try {
693  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
694  newCurrentCase.open(isNewCase);
695  currentCase = newCurrentCase;
696  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
698  updateGUIForCaseOpened(newCurrentCase);
699  }
700  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
701  } catch (CaseActionCancelledException ex) {
702  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
703  throw ex;
704  } catch (CaseActionException ex) {
705  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
706  throw ex;
707  }
708  }
709  }
710 
719  private static String displayNameToUniqueName(String caseDisplayName) {
720  /*
721  * Replace all non-ASCII characters.
722  */
723  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
724 
725  /*
726  * Replace all control characters.
727  */
728  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
729 
730  /*
731  * Replace /, \, :, ?, space, ' ".
732  */
733  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
734 
735  /*
736  * Make it all lowercase.
737  */
738  uniqueCaseName = uniqueCaseName.toLowerCase();
739 
740  /*
741  * Add a time stamp for uniqueness.
742  */
743  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
744  Date date = new Date();
745  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
746 
747  return uniqueCaseName;
748  }
749 
758  static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
759 
760  File caseDirF = new File(caseDir);
761 
762  if (caseDirF.exists()) {
763  if (caseDirF.isFile()) {
764  throw new CaseActionException(
765  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
766 
767  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
768  throw new CaseActionException(
769  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
770  }
771  }
772 
773  try {
774  boolean result = (caseDirF).mkdirs(); // create root case Directory
775 
776  if (result == false) {
777  throw new CaseActionException(
778  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
779  }
780 
781  // create the folders inside the case directory
782  String hostClause = "";
783 
784  if (caseType == CaseType.MULTI_USER_CASE) {
785  hostClause = File.separator + NetworkUtils.getLocalHostName();
786  }
787  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
788  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
789  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
790  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
791 
792  if (result == false) {
793  throw new CaseActionException(
794  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
795  }
796 
797  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
798  result = new File(modulesOutDir).mkdir();
799 
800  if (result == false) {
801  throw new CaseActionException(
802  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
803  modulesOutDir));
804  }
805 
806  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
807  result = new File(reportsOutDir).mkdir();
808 
809  if (result == false) {
810  throw new CaseActionException(
811  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
812  modulesOutDir));
813 
814  }
815 
816  } catch (MissingResourceException | CaseActionException e) {
817  throw new CaseActionException(
818  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
819  }
820  }
821 
829  static Map<Long, String> getImagePaths(SleuthkitCase db) {
830  Map<Long, String> imgPaths = new HashMap<>();
831  try {
832  Map<Long, List<String>> imgPathsList = db.getImagePaths();
833  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
834  if (entry.getValue().size() > 0) {
835  imgPaths.put(entry.getKey(), entry.getValue().get(0));
836  }
837  }
838  } catch (TskCoreException ex) {
839  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
840  }
841  return imgPaths;
842  }
843 
860  @Messages({
861  "Case.progressMessage.deletingTextIndex=Deleting text index...",
862  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
863  "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
864  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
865  })
866  private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
867  boolean errorsOccurred = false;
868  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
869  /*
870  * Delete the case database from the database server.
871  */
872  try {
873  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
874  CaseDbConnectionInfo db;
876  Class.forName("org.postgresql.Driver"); //NON-NLS
877  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
878  Statement statement = connection.createStatement();) {
879  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
880  statement.execute(deleteCommand);
881  }
882  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
883  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);
884  errorsOccurred = true;
885  }
886  }
887 
888  /*
889  * Delete the text index.
890  */
891  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
892  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
893  try {
894  searchService.deleteTextIndex(metadata);
895  } catch (KeywordSearchServiceException ex) {
896  logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
897  errorsOccurred = true;
898  }
899  }
900 
901  /*
902  * Delete the case directory.
903  */
904  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
905  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
906  logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
907  errorsOccurred = true;
908  }
909 
910  /*
911  * If running in a GUI, remove the case from the Recent Cases menu
912  */
914  SwingUtilities.invokeLater(() -> {
915  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
916  });
917  }
918 
919  if (errorsOccurred) {
920  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
921  }
922  }
923 
934  @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
936  try {
937  String resourcesNodeName = caseDir + "_resources";
938  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
939  if (null == lock) {
940  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
941  }
942  return lock;
943  } catch (InterruptedException ex) {
944  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
945  } catch (CoordinationServiceException ex) {
946  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
947  }
948  }
949 
953  private static void updateGUIForCaseOpened(Case newCurrentCase) {
955  SwingUtilities.invokeLater(() -> {
956  /*
957  * If the case database was upgraded for a new schema and a
958  * backup database was created, notify the user.
959  */
960  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
961  String backupDbPath = caseDb.getBackupDatabasePath();
962  if (null != backupDbPath) {
963  JOptionPane.showMessageDialog(
964  mainFrame,
965  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
966  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
967  JOptionPane.INFORMATION_MESSAGE);
968  }
969 
970  /*
971  * Look for the files for the data sources listed in the case
972  * database and give the user the opportunity to locate any that
973  * are missing.
974  */
975  Map<Long, String> imgPaths = getImagePaths(caseDb);
976  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
977  long obj_id = entry.getKey();
978  String path = entry.getValue();
979  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
980  if (!fileExists) {
981  int response = JOptionPane.showConfirmDialog(
982  mainFrame,
983  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
984  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
985  JOptionPane.YES_NO_OPTION);
986  if (response == JOptionPane.YES_OPTION) {
987  MissingImageDialog.makeDialog(obj_id, caseDb);
988  } else {
989  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
990 
991  }
992  }
993  }
994 
995  /*
996  * Enable the case-specific actions.
997  */
998  CallableSystemAction.get(AddImageAction.class).setEnabled(true);
999  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1000  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
1001  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
1002  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1003  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1004 
1005  /*
1006  * Add the case to the recent cases tracker that supplies a list
1007  * of recent cases to the recent cases menu item and the
1008  * open/create case dialog.
1009  */
1010  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1011 
1012  /*
1013  * Open the top components (windows within the main application
1014  * window).
1015  */
1016  if (newCurrentCase.hasData()) {
1018  }
1019 
1020  /*
1021  * Reset the main window title to:
1022  *
1023  * [curent case display name] - [application name].
1024  */
1025  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + UserPreferences.getAppName());
1026  });
1027  }
1028  }
1029 
1030  /*
1031  * Update the GUI to to reflect the lack of a current case.
1032  */
1033  private static void updateGUIForCaseClosed() {
1035  SwingUtilities.invokeLater(() -> {
1036  /*
1037  * Close the top components (windows within the main application
1038  * window).
1039  */
1041 
1042  /*
1043  * Disable the case-specific menu items.
1044  */
1045  CallableSystemAction
1046  .get(AddImageAction.class
1047  ).setEnabled(false);
1048  CallableSystemAction
1049  .get(CaseCloseAction.class
1050  ).setEnabled(false);
1051  CallableSystemAction
1052  .get(CasePropertiesAction.class
1053  ).setEnabled(false);
1054  CallableSystemAction
1055  .get(CaseDeleteAction.class
1056  ).setEnabled(false);
1057  CallableSystemAction
1058  .get(OpenTimelineAction.class
1059  ).setEnabled(false);
1060  CallableSystemAction
1061  .get(OpenOutputFolderAction.class
1062  ).setEnabled(false);
1063 
1064  /*
1065  * Clear the notifications in the notfier component in the lower
1066  * right hand corner of the main application window.
1067  */
1069 
1070  /*
1071  * Reset the main window title to be just the application name,
1072  * instead of [curent case display name] - [application name].
1073  */
1074  mainFrame.setTitle(UserPreferences.getAppName());
1075  });
1076  }
1077  }
1078 
1082  private static void clearTempSubDir(String tempSubDirPath) {
1083  File tempFolder = new File(tempSubDirPath);
1084  if (tempFolder.isDirectory()) {
1085  File[] files = tempFolder.listFiles();
1086  if (files.length > 0) {
1087  for (File file : files) {
1088  if (file.isDirectory()) {
1089  FileUtil.deleteDir(file);
1090  } else {
1091  file.delete();
1092  }
1093  }
1094  }
1095  }
1096  }
1097 
1103  public SleuthkitCase getSleuthkitCase() {
1104  return this.caseDb;
1105  }
1106 
1113  return caseServices;
1114  }
1115 
1122  return metadata.getCaseType();
1123  }
1124 
1130  public String getCreatedDate() {
1131  return metadata.getCreatedDate();
1132  }
1133 
1139  public String getName() {
1140  return metadata.getCaseName();
1141  }
1142 
1148  public String getDisplayName() {
1149  return metadata.getCaseDisplayName();
1150  }
1151 
1157  public String getNumber() {
1158  return metadata.getCaseNumber();
1159  }
1160 
1166  public String getExaminer() {
1167  return metadata.getExaminer();
1168  }
1169 
1175  public String getCaseDirectory() {
1176  return metadata.getCaseDirectory();
1177  }
1178 
1187  public String getOutputDirectory() {
1188  String caseDirectory = getCaseDirectory();
1189  Path hostPath;
1190  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1191  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1192  } else {
1193  hostPath = Paths.get(caseDirectory);
1194  }
1195  if (!hostPath.toFile().exists()) {
1196  hostPath.toFile().mkdirs();
1197  }
1198  return hostPath.toString();
1199  }
1200 
1207  public String getTempDirectory() {
1208  return getOrCreateSubdirectory(TEMP_FOLDER);
1209  }
1210 
1217  public String getCacheDirectory() {
1218  return getOrCreateSubdirectory(CACHE_FOLDER);
1219  }
1220 
1227  public String getExportDirectory() {
1228  return getOrCreateSubdirectory(EXPORT_FOLDER);
1229  }
1230 
1237  public String getLogDirectoryPath() {
1238  return getOrCreateSubdirectory(LOG_FOLDER);
1239  }
1240 
1247  public String getReportDirectory() {
1248  return getOrCreateSubdirectory(REPORTS_FOLDER);
1249  }
1250 
1257  public String getModuleDirectory() {
1258  return getOrCreateSubdirectory(MODULE_FOLDER);
1259  }
1260 
1269  Path path = Paths.get(getModuleDirectory());
1271  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1272  } else {
1273  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1274  }
1275  }
1276 
1286  public List<Content> getDataSources() throws TskCoreException {
1287  List<Content> list = caseDb.getRootObjects();
1288  hasDataSources = (list.size() > 0);
1289  return list;
1290  }
1291 
1297  public Set<TimeZone> getTimeZones() {
1298  Set<TimeZone> timezones = new HashSet<>();
1299  try {
1300  for (Content c : getDataSources()) {
1301  final Content dataSource = c.getDataSource();
1302  if ((dataSource != null) && (dataSource instanceof Image)) {
1303  Image image = (Image) dataSource;
1304  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1305  }
1306  }
1307  } catch (TskCoreException ex) {
1308  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1309  }
1310  return timezones;
1311  }
1312 
1319  public String getTextIndexName() {
1320  return getMetadata().getTextIndexName();
1321  }
1322 
1329  public boolean hasData() {
1330  if (!hasDataSources) {
1331  try {
1332  hasDataSources = (getDataSources().size() > 0);
1333  } catch (TskCoreException ex) {
1334  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1335  }
1336  }
1337  return hasDataSources;
1338  }
1339 
1350  public void notifyAddingDataSource(UUID eventId) {
1351  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1352  }
1353 
1364  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1365  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1366  }
1367 
1379  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1380  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1381  }
1382 
1390  public void notifyContentTagAdded(ContentTag newTag) {
1391  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1392  }
1393 
1401  public void notifyContentTagDeleted(ContentTag deletedTag) {
1402  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1403  }
1404 
1412  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1413  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1414  }
1415 
1423  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1424  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1425  }
1426 
1438  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1439  String normalizedLocalPath;
1440  try {
1441  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1442  } catch (InvalidPathException ex) {
1443  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1444  throw new TskCoreException(errorMsg, ex);
1445  }
1446  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName);
1447  eventPublisher.publish(new ReportAddedEvent(report));
1448  }
1449 
1458  public List<Report> getAllReports() throws TskCoreException {
1459  return this.caseDb.getAllReports();
1460  }
1461 
1470  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1471  for (Report report : reports) {
1472  this.caseDb.deleteReport(report);
1473  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1474  }
1475  }
1476 
1482  CaseMetadata getMetadata() {
1483  return metadata;
1484  }
1485 
1489  @Messages({
1490  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata, cannot change case display name."
1491  })
1492  void updateDisplayName(String newDisplayName) throws CaseActionException {
1493  String oldDisplayName = metadata.getCaseDisplayName();
1494  try {
1495  metadata.setCaseDisplayName(newDisplayName);
1496  } catch (CaseMetadataException ex) {
1497  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError());
1498  }
1499  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldDisplayName, newDisplayName));
1500  if (RuntimeProperties.runningWithGUI()) {
1501  SwingUtilities.invokeLater(() -> {
1502  mainFrame.setTitle(newDisplayName + " - " + UserPreferences.getAppName());
1503  try {
1504  RecentCases.getInstance().updateRecentCase(oldDisplayName, metadata.getFilePath().toString(), newDisplayName, metadata.getFilePath().toString());
1505  } catch (Exception ex) {
1506  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1507  }
1508  });
1509  }
1510  }
1511 
1526  private Case(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner) {
1527  metadata = new CaseMetadata(caseDir, caseType, displayNameToUniqueName(caseDisplayName), caseDisplayName, caseNumber, examiner);
1528  }
1529 
1535  private Case(CaseMetadata caseMetaData) {
1536  metadata = caseMetaData;
1537  }
1538 
1554  @Messages({
1555  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1556  "Case.progressIndicatorTitle.openingCase=Opening Case",
1557  "Case.progressIndicatorCancelButton.label=Cancel",
1558  "Case.progressMessage.preparing=Preparing...",
1559  "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1560  "Case.progressMessage.cancelling=Cancelling...",
1561  "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1562  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1563  })
1564  private void open(boolean isNewCase) throws CaseActionException {
1565  /*
1566  * Create and start either a GUI progress indicator with a Cancel button
1567  * or a logging progress indicator.
1568  */
1569  CancelButtonListener cancelButtonListener = null;
1570  ProgressIndicator progressIndicator;
1572  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1573  String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1574  progressIndicator = new ModalDialogProgressIndicator(
1575  mainFrame,
1576  progressIndicatorTitle,
1577  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1578  Bundle.Case_progressIndicatorCancelButton_label(),
1579  cancelButtonListener);
1580  } else {
1581  progressIndicator = new LoggingProgressIndicator();
1582  }
1583  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1584 
1585  /*
1586  * Creating/opening a case is always done by creating a task running in
1587  * the same non-UI thread that will be used to close the case, so a
1588  * single-threaded executor service is created here and saved as case
1589  * state (must be volatile for cancellation to work).
1590  *
1591  * --- If the case is a single-user case, this supports cancelling
1592  * opening of the case by cancelling the task.
1593  *
1594  * --- If the case is a multi-user case, this still supports
1595  * cancellation, but it also makes it possible for the shared case
1596  * directory lock held as long as the case is open to be released in the
1597  * same thread in which it was acquired, as is required by the
1598  * coordination service.
1599  */
1600  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1601  caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1602  Future<Void> future = caseLockingExecutor.submit(() -> {
1603  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1604  open(isNewCase, progressIndicator);
1605  } else {
1606  /*
1607  * First, acquire a shared case directory lock that will be held
1608  * as long as this node has this case open. This will prevent
1609  * deletion of the case by another node. Next, acquire an
1610  * exclusive case resources lock to ensure only one node at a
1611  * time can create/open/upgrade/close the case resources.
1612  */
1613  progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1616  assert (null != resourcesLock);
1617  open(isNewCase, progressIndicator);
1618  } catch (CaseActionException ex) {
1619  releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
1620  throw ex;
1621  }
1622  }
1623  return null;
1624  });
1625  if (null != cancelButtonListener) {
1626  cancelButtonListener.setCaseActionFuture(future);
1627  }
1628 
1629  /*
1630  * Wait for the case creation/opening task to finish.
1631  */
1632  try {
1633  future.get();
1634  } catch (InterruptedException discarded) {
1635  /*
1636  * The thread this method is running in has been interrupted. Cancel
1637  * the create/open task, wait for it to finish, and shut down the
1638  * executor. This can be done safely because if the task is
1639  * completed with a cancellation condition, the case will have been
1640  * closed and the case directory lock released will have been
1641  * released.
1642  */
1643  if (null != cancelButtonListener) {
1644  cancelButtonListener.actionPerformed(null);
1645  } else {
1646  future.cancel(true);
1647  }
1648  Case.shutDownTaskExecutor(caseLockingExecutor);
1649  } catch (CancellationException discarded) {
1650  /*
1651  * The create/open task has been cancelled. Wait for it to finish,
1652  * and shut down the executor. This can be done safely because if
1653  * the task is completed with a cancellation condition, the case
1654  * will have been closed and the case directory lock released will
1655  * have been released.
1656  */
1657  Case.shutDownTaskExecutor(caseLockingExecutor);
1658  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1659  } catch (ExecutionException ex) {
1660  /*
1661  * The create/open task has thrown an exception. Wait for it to
1662  * finish, and shut down the executor. This can be done safely
1663  * because if the task is completed with an execution condition, the
1664  * case will have been closed and the case directory lock released
1665  * will have been released.
1666  */
1667  Case.shutDownTaskExecutor(caseLockingExecutor);
1668  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1669  } finally {
1670  progressIndicator.finish();
1671  }
1672  }
1673 
1685  private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
1686  try {
1687  if (Thread.currentThread().isInterrupted()) {
1688  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1689  }
1690 
1691  if (isNewCase) {
1692  createCaseData(progressIndicator);
1693  } else {
1694  openCaseData(progressIndicator);
1695  }
1696 
1697  if (Thread.currentThread().isInterrupted()) {
1698  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1699  }
1700 
1701  openServices(progressIndicator);
1702 
1703  if (Thread.currentThread().isInterrupted()) {
1704  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1705  }
1706  } catch (CaseActionException ex) {
1707  /*
1708  * Cancellation or failure. Clean up. The sleep is a little hack to
1709  * clear the interrupted flag for this thread if this is a
1710  * cancellation scenario, so that the clean up can run to completion
1711  * in this thread.
1712  */
1713  try {
1714  Thread.sleep(1);
1715  } catch (InterruptedException discarded) {
1716  }
1717  close(progressIndicator);
1718  throw ex;
1719  }
1720  }
1721 
1732  @Messages({
1733  "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1734  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1735  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}",
1736  "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
1737  })
1738  private void createCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1739  /*
1740  * Create the case directory, if it does not already exist.
1741  *
1742  * TODO (JIRA-2180): Always create the case directory as part of the
1743  * case creation process.
1744  */
1745  if (new File(metadata.getCaseDirectory()).exists() == false) {
1746  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1747  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
1748  }
1749 
1750  /*
1751  * Create the case database.
1752  */
1753  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1754  try {
1755  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1756  /*
1757  * For single-user cases, the case database is a SQLite database
1758  * with a standard name, physically located in the root of the
1759  * case directory.
1760  */
1761  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
1762  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
1763  } else {
1764  /*
1765  * For multi-user cases, the case database is a PostgreSQL
1766  * database with a name derived from the case display name,
1767  * physically located on a database server.
1768  */
1769  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1770  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
1771  }
1772  } catch (TskCoreException ex) {
1773  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
1774  } catch (UserPreferencesException ex) {
1775  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
1776  } catch (CaseMetadataException ex) {
1777  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1778  }
1779  }
1780 
1791  @Messages({
1792  "Case.progressMessage.openingCaseDatabase=Opening case database...",
1793  "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database.",
1794  "Case.unsupportedSchemaVersionMessage=Unsupported DB schema version - see log for details",
1795  "Case.databaseConnectionInfo.error.msg=Error accessing database server connection info. See Tools, Options, Multi-User.",
1796  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. "
1797  + "See Tools, Options, Multi-user."
1798  })
1799  private void openCaseData(ProgressIndicator progressIndicator) throws CaseActionException {
1800  try {
1801  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
1802  String databaseName = metadata.getCaseDatabaseName();
1803  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1804  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
1806  try {
1807  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
1808  } catch (UserPreferencesException ex) {
1809  throw new CaseActionException(Case_databaseConnectionInfo_error_msg(), ex);
1810  }
1811  } else {
1812  throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled());
1813  }
1814  } catch (TskUnsupportedSchemaVersionException ex) {
1815  throw new CaseActionException(Bundle.Case_unsupportedSchemaVersionMessage(), ex);
1816  } catch (TskCoreException ex) {
1817  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
1818  }
1819  }
1820 
1829  @Messages({
1830  "Case.progressMessage.switchingLogDirectory=Switching log directory...",
1831  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
1832  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
1833  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
1834  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
1835  private void openServices(ProgressIndicator progressIndicator) throws CaseActionException {
1836  /*
1837  * Switch to writing to the application logs in the logs subdirectory of
1838  * the case directory.
1839  */
1840  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
1842  if (Thread.currentThread().isInterrupted()) {
1843  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1844  }
1845 
1846  /*
1847  * Clear the temp subdirectory of the case directory.
1848  */
1849  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
1851  if (Thread.currentThread().isInterrupted()) {
1852  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1853  }
1854 
1855  /*
1856  * Open the case-level services.
1857  */
1858  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
1859  this.caseServices = new Services(caseDb);
1860  if (Thread.currentThread().isInterrupted()) {
1861  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1862  }
1863 
1864  /*
1865  * Allow any registered application services to open any resources
1866  * specific to this case.
1867  */
1868  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
1870  if (Thread.currentThread().isInterrupted()) {
1871  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1872  }
1873 
1874  /*
1875  * If this case is a multi-user case, set up for communication with
1876  * other nodes.
1877  */
1878  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1879  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
1880  try {
1881  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
1882  if (Thread.currentThread().isInterrupted()) {
1883  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1884  }
1885  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
1886  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
1887  /*
1888  * The collaboration monitor and event channel are not
1889  * essential. Log an error and notify the user, but do not
1890  * throw.
1891  */
1892  logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
1894  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
1895  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"),
1896  NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")));
1897  }
1898  }
1899  }
1900  }
1901 
1906  @NbBundle.Messages({
1907  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
1908  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
1909  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
1910  })
1912  /*
1913  * Each service gets its own independently cancellable/interruptible
1914  * task, running in a named thread managed by an executor service, with
1915  * its own progress indicator. This allows for cancellation of the
1916  * opening of case resources for individual services. It also makes it
1917  * possible to ensure that each service task completes before the next
1918  * one starts by awaiting termination of the executor service.
1919  */
1920  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
1921  /*
1922  * Create a progress indicator for the task and start the task. If
1923  * running with a GUI, the progress indicator will be a dialog box
1924  * with a Cancel button.
1925  */
1926  CancelButtonListener cancelButtonListener = null;
1927  ProgressIndicator progressIndicator;
1929  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
1930  progressIndicator = new ModalDialogProgressIndicator(
1931  mainFrame,
1932  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
1933  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1934  Bundle.Case_progressIndicatorCancelButton_label(),
1935  cancelButtonListener);
1936  } else {
1937  progressIndicator = new LoggingProgressIndicator();
1938  }
1939  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1940  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
1941  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
1942  threadNameSuffix = threadNameSuffix.toLowerCase();
1943  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
1944  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
1945  Future<Void> future = executor.submit(() -> {
1946  service.openCaseResources(context);
1947  return null;
1948  });
1949  if (null != cancelButtonListener) {
1950  cancelButtonListener.setCaseContext(context);
1951  cancelButtonListener.setCaseActionFuture(future);
1952  }
1953 
1954  /*
1955  * Wait for the task to either be completed or
1956  * cancelled/interrupted, or for the opening of the case to be
1957  * cancelled.
1958  */
1959  try {
1960  future.get();
1961  } catch (InterruptedException discarded) {
1962  /*
1963  * The parent create/open case task has been cancelled.
1964  */
1965  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()));
1966  future.cancel(true);
1967  } catch (CancellationException discarded) {
1968  /*
1969  * The opening of case resources by the application service has
1970  * been cancelled, so the executor service has thrown. Note that
1971  * there is no guarantee the task itself has responded to the
1972  * cancellation request yet.
1973  */
1974  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()));
1975  } catch (ExecutionException ex) {
1976  /*
1977  * An exception was thrown while executing the task. The
1978  * case-specific application service resources are not
1979  * essential. Log an error and notify the user if running the
1980  * desktop GUI, but do not throw.
1981  */
1982  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
1984  SwingUtilities.invokeLater(() -> {
1985  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
1986  });
1987  }
1988  } finally {
1989  /*
1990  * Shut down the executor service and wait for it to finish.
1991  * This ensures that the task has finished. Without this, it
1992  * would be possible to start the next task before the current
1993  * task responded to a cancellation request.
1994  */
1995  shutDownTaskExecutor(executor);
1996  progressIndicator.finish();
1997  }
1998 
1999  if (Thread.currentThread().isInterrupted()) {
2000  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2001  }
2002  }
2003  }
2004 
2008  private void close() throws CaseActionException {
2009  /*
2010  * Set up either a GUI progress indicator without a Cancel button or a
2011  * logging progress indicator.
2012  */
2013  ProgressIndicator progressIndicator;
2015  progressIndicator = new ModalDialogProgressIndicator(
2016  mainFrame,
2017  Bundle.Case_progressIndicatorTitle_closingCase());
2018  } else {
2019  progressIndicator = new LoggingProgressIndicator();
2020  }
2021  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2022 
2023  /*
2024  * Closing a case is always done in the same non-UI thread that
2025  * opened/created the case. If the case is a multi-user case, this
2026  * ensures that case directory lock that is held as long as the case is
2027  * open is released in the same thread in which it was acquired, as is
2028  * required by the coordination service.
2029  */
2030  Future<Void> future = caseLockingExecutor.submit(() -> {
2031  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2032  close(progressIndicator);
2033  } else {
2034  /*
2035  * Acquire an exclusive case resources lock to ensure only one
2036  * node at a time can create/open/upgrade/close the case
2037  * resources.
2038  */
2039  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2041  assert (null != resourcesLock);
2042  close(progressIndicator);
2043  } finally {
2044  /*
2045  * Always release the case directory lock that was acquired
2046  * when the case was opened.
2047  */
2049  }
2050  }
2051  return null;
2052  });
2053 
2054  try {
2055  future.get();
2056  } catch (InterruptedException | CancellationException unused) {
2057  /*
2058  * The wait has been interrupted by interrupting the thread running
2059  * this method. Not allowing cancellation of case closing, so ignore
2060  * the interrupt. Likewsie, cancellation of the case closing task is
2061  * not supported.
2062  */
2063  } catch (ExecutionException ex) {
2064  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2065  } finally {
2066  shutDownTaskExecutor(caseLockingExecutor);
2067  progressIndicator.finish();
2068  }
2069  }
2070 
2076  @Messages({
2077  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2078  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2079  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2080  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2081  })
2082  private void close(ProgressIndicator progressIndicator) {
2084 
2085  /*
2086  * Stop sending/receiving case events to and from other nodes if this is
2087  * a multi-user case.
2088  */
2089  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2090  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2091  if (null != collaborationMonitor) {
2092  collaborationMonitor.shutdown();
2093  }
2094  eventPublisher.closeRemoteEventChannel();
2095  }
2096 
2097  /*
2098  * Allow all registered application services providers to close
2099  * resources related to the case.
2100  */
2101  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2103 
2104  /*
2105  * Close the case-level services.
2106  */
2107  if (null != caseServices) {
2108  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2109  try {
2110  this.caseServices.close();
2111  } catch (IOException ex) {
2112  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2113  }
2114  }
2115 
2116  /*
2117  * Close the case database
2118  */
2119  if (null != caseDb) {
2120  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2121  caseDb.close();
2122  }
2123 
2124  /*
2125  * Switch the log directory.
2126  */
2127  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2129  }
2130 
2135  @Messages({
2136  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2137  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2138  })
2140  /*
2141  * Each service gets its own independently cancellable task, and thus
2142  * its own task progress indicator.
2143  */
2144  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2145  ProgressIndicator progressIndicator;
2147  progressIndicator = new ModalDialogProgressIndicator(
2148  mainFrame,
2149  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2150  } else {
2151  progressIndicator = new LoggingProgressIndicator();
2152  }
2153  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2154  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2155  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2156  threadNameSuffix = threadNameSuffix.toLowerCase();
2157  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2158  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2159  Future<Void> future = executor.submit(() -> {
2160  service.closeCaseResources(context);
2161  return null;
2162  });
2163  try {
2164  future.get();
2165  } catch (InterruptedException ex) {
2166  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2167  } catch (CancellationException ex) {
2168  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2169  } catch (ExecutionException ex) {
2170  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2172  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2173  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2174  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2175  }
2176  } finally {
2177  shutDownTaskExecutor(executor);
2178  progressIndicator.finish();
2179  }
2180  }
2181  }
2182 
2191  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2192  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2193  try {
2194  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2195  if (null == caseDirLock) {
2196  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2197  }
2198  } catch (InterruptedException | CoordinationServiceException ex) {
2199  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2200  }
2201  }
2202 
2208  private void releaseSharedCaseDirLock(String caseDir) {
2209  if (caseDirLock != null) {
2210  try {
2211  caseDirLock.release();
2212  caseDirLock = null;
2214  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2215  }
2216  }
2217  }
2218 
2225  private String getOrCreateSubdirectory(String subDirectoryName) {
2226  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2227  if (!subDirectory.exists()) {
2228  subDirectory.mkdirs();
2229  }
2230  return subDirectory.toString();
2231 
2232  }
2233 
2242  private static void shutDownTaskExecutor(ExecutorService executor) {
2243  executor.shutdown();
2244  boolean taskCompleted = false;
2245  while (!taskCompleted) {
2246  try {
2247  taskCompleted = executor.awaitTermination(EXECUTOR_AWAIT_TIMEOUT_SECS, TimeUnit.SECONDS);
2248  } catch (InterruptedException ignored) {
2249  /*
2250  * The current policy is to wait for the task to finish so that
2251  * the case can be left in a consistent state.
2252  *
2253  * For a specific example of the motivation for this policy,
2254  * note that a application service (Solr search service)
2255  * experienced an error condition when opening case resources
2256  * that left the service blocked uninterruptibly on a socket
2257  * read. This eventually led to a mysterious "freeze" as the
2258  * user-cancelled service task continued to run holdiong a lock
2259  * that a UI thread soon tried to acquire. Thus it has been
2260  * deemed better to make the "freeze" happen in a more
2261  * informative way, i.e., with the progress indicator for the
2262  * unfinished task on the screen, if a similar error condition
2263  * arises again.
2264  */
2265  }
2266  }
2267  }
2268 
2273  @ThreadSafe
2274  private final static class CancelButtonListener implements ActionListener {
2275 
2276  private final String cancellationMessage;
2277  @GuardedBy("this")
2278  private boolean cancelRequested;
2279  @GuardedBy("this")
2281  @GuardedBy("this")
2282  private Future<?> caseActionFuture;
2283 
2292  private CancelButtonListener(String cancellationMessage) {
2293  this.cancellationMessage = cancellationMessage;
2294  }
2295 
2301  private synchronized void setCaseContext(CaseContext caseContext) {
2302  this.caseContext = caseContext;
2303  /*
2304  * If the cancel button has already been pressed, pass the
2305  * cancellation on to the case context.
2306  */
2307  if (cancelRequested) {
2308  cancel();
2309  }
2310  }
2311 
2317  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
2318  this.caseActionFuture = caseActionFuture;
2319  /*
2320  * If the cancel button has already been pressed, cancel the Future
2321  * of the task.
2322  */
2323  if (cancelRequested) {
2324  cancel();
2325  }
2326  }
2327 
2333  @Override
2334  public synchronized void actionPerformed(ActionEvent event) {
2335  cancel();
2336  }
2337 
2341  private void cancel() {
2342  /*
2343  * At a minimum, set the cancellation requested flag of this
2344  * listener.
2345  */
2346  this.cancelRequested = true;
2347  if (null != this.caseContext) {
2348  /*
2349  * Set the cancellation request flag and display the
2350  * cancellation message in the progress indicator for the case
2351  * context associated with this listener.
2352  */
2354  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
2355  if (progressIndicator instanceof ModalDialogProgressIndicator) {
2356  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2357  }
2358  }
2359  this.caseContext.requestCancel();
2360  }
2361  if (null != this.caseActionFuture) {
2362  /*
2363  * Cancel the Future of the task associated with this listener.
2364  * Note that the task thread will be interrupted if the task is
2365  * blocked.
2366  */
2367  this.caseActionFuture.cancel(true);
2368  }
2369  }
2370  }
2371 
2375  private static class TaskThreadFactory implements ThreadFactory {
2376 
2377  private final String threadName;
2378 
2379  private TaskThreadFactory(String threadName) {
2380  this.threadName = threadName;
2381  }
2382 
2383  @Override
2384  public Thread newThread(Runnable task) {
2385  return new Thread(task, threadName);
2386  }
2387 
2388  }
2389 
2397  @Deprecated
2398  public static String getAppName() {
2399  return UserPreferences.getAppName();
2400  }
2401 
2421  @Deprecated
2422  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2423  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2424  }
2425 
2446  @Deprecated
2447  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2448  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2449  }
2450 
2462  @Deprecated
2463  public static void open(String caseMetadataFilePath) throws CaseActionException {
2464  openAsCurrentCase(caseMetadataFilePath);
2465  }
2466 
2476  @Deprecated
2477  public void closeCase() throws CaseActionException {
2478  closeCurrentCase();
2479  }
2480 
2486  @Deprecated
2487  public static void invokeStartupDialog() {
2489  }
2490 
2504  @Deprecated
2505  public static String convertTimeZone(String timeZoneId) {
2506  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2507  }
2508 
2518  @Deprecated
2519  public static boolean pathExists(String filePath) {
2520  return new File(filePath).isFile();
2521  }
2522 
2531  @Deprecated
2532  public static String getAutopsyVersion() {
2533  return Version.getVersion();
2534  }
2535 
2543  @Deprecated
2544  public static boolean existsCurrentCase() {
2545  return isCaseOpen();
2546  }
2547 
2557  @Deprecated
2558  public static String getModulesOutputDirRelPath() {
2559  return "ModuleOutput"; //NON-NLS
2560  }
2561 
2571  @Deprecated
2572  public static PropertyChangeSupport
2574  return new PropertyChangeSupport(Case.class
2575  );
2576  }
2577 
2586  @Deprecated
2587  public String getModulesOutputDirAbsPath() {
2588  return getModuleDirectory();
2589  }
2590 
2605  @Deprecated
2606  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
2607  try {
2608  Image newDataSource = caseDb.getImageById(imgId);
2609  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
2610  return newDataSource;
2611  } catch (TskCoreException ex) {
2612  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
2613  }
2614  }
2615 
2623  @Deprecated
2624  public Set<TimeZone> getTimeZone() {
2625  return getTimeZones();
2626  }
2627 
2638  @Deprecated
2639  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
2640  deleteReports(reports);
2641  }
2642 
2643 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:135
List< Content > getDataSources()
Definition: Case.java:1286
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1401
Case(CaseMetadata caseMetaData)
Definition: Case.java:1535
static CaseType fromString(String typeName)
Definition: Case.java:178
static void shutDownTaskExecutor(ExecutorService executor)
Definition: Case.java:2242
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:132
Case(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:1526
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1423
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:2606
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:935
static boolean existsCurrentCase()
Definition: Case.java:2544
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:370
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:134
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:122
static final String EXPORT_FOLDER
Definition: Case.java:126
static volatile Frame mainFrame
Definition: Case.java:137
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2505
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:125
void createCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1738
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1438
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:953
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2447
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:123
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:627
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:2639
volatile ExecutorService caseLockingExecutor
Definition: Case.java:140
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:2301
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1082
static boolean isValidName(String caseName)
Definition: Case.java:454
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2192
CollaborationMonitor collaborationMonitor
Definition: Case.java:143
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2208
static String getModulesOutputDirRelPath()
Definition: Case.java:2558
void openCaseData(ProgressIndicator progressIndicator)
Definition: Case.java:1799
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:2334
static final String MODULE_FOLDER
Definition: Case.java:130
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2422
synchronized void openRemoteEventChannel(String channelName)
void openServices(ProgressIndicator progressIndicator)
Definition: Case.java:1835
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:719
void open(boolean isNewCase)
Definition: Case.java:1564
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:512
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:430
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:121
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2082
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1412
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2573
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:358
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2317
static final long EXECUTOR_AWAIT_TIMEOUT_SECS
Definition: Case.java:131
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:420
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1470
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1379
static boolean pathExists(String filePath)
Definition: Case.java:2519
static void open(String caseMetadataFilePath)
Definition: Case.java:2463
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:679
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2225
boolean equalsName(String otherTypeName)
Definition: Case.java:236
static final String EVENT_CHANNEL_NAME
Definition: Case.java:124
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:395
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:127
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:138
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1350
CoordinationService.Lock caseDirLock
Definition: Case.java:141
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:410
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1390
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:133
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:485
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1364
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:440
static final Object caseActionSerializationLock
Definition: Case.java:136
void open(boolean isNewCase, ProgressIndicator progressIndicator)
Definition: Case.java:1685
static final String REPORTS_FOLDER
Definition: Case.java:128
static final String TEMP_FOLDER
Definition: Case.java:129
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:385
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:866

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