Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Case.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2019 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 
22 import java.awt.Frame;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.beans.PropertyChangeListener;
26 import java.beans.PropertyChangeSupport;
27 import java.io.File;
28 import java.io.IOException;
29 import java.nio.file.InvalidPathException;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.sql.Connection;
33 import java.sql.DriverManager;
34 import java.sql.SQLException;
35 import java.sql.Statement;
36 import java.text.ParseException;
37 import java.text.SimpleDateFormat;
38 import java.util.Collection;
39 import java.util.Date;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.TimeZone;
46 import java.util.UUID;
47 import java.util.concurrent.CancellationException;
48 import java.util.concurrent.ExecutionException;
49 import java.util.concurrent.ExecutorService;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.Future;
52 import java.util.concurrent.ThreadFactory;
53 import java.util.concurrent.TimeUnit;
54 import java.util.logging.Level;
55 import java.util.stream.Collectors;
56 import java.util.stream.Stream;
57 import javax.annotation.concurrent.GuardedBy;
58 import javax.annotation.concurrent.ThreadSafe;
59 import javax.swing.JOptionPane;
60 import javax.swing.SwingUtilities;
61 import org.openide.util.Lookup;
62 import org.openide.util.NbBundle;
63 import org.openide.util.NbBundle.Messages;
64 import org.openide.util.actions.CallableSystemAction;
65 import org.openide.windows.WindowManager;
69 import static org.sleuthkit.autopsy.casemodule.Bundle.*;
113 import org.sleuthkit.datamodel.BlackboardArtifactTag;
114 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
115 import org.sleuthkit.datamodel.Content;
116 import org.sleuthkit.datamodel.ContentTag;
117 import org.sleuthkit.datamodel.Image;
118 import org.sleuthkit.datamodel.Report;
119 import org.sleuthkit.datamodel.SleuthkitCase;
120 import org.sleuthkit.datamodel.TskCoreException;
121 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
123 
127 public class Case {
128 
129  private static final int DIR_LOCK_TIMOUT_HOURS = 12;
130  private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12;
131  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
132  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
133  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
134  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
135  private static final String LOG_FOLDER = "Log"; //NON-NLS
136  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
137  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
138  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
139  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
140  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
141  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
142  private static final Logger logger = Logger.getLogger(Case.class.getName());
144  private static final Object caseActionSerializationLock = new Object();
145  private static volatile Frame mainFrame;
146  private static volatile Case currentCase;
147  private final CaseMetadata metadata;
148  private volatile ExecutorService caseLockingExecutor;
150  private SleuthkitCase caseDb;
151  private CollaborationMonitor collaborationMonitor;
153  private boolean hasDataSources;
154 
155  /*
156  * Get a reference to the main window of the desktop application to use to
157  * parent pop up dialogs and initialize the application name for use in
158  * changing the main window title.
159  */
160  static {
161  WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
162  @Override
163  public void run() {
164  mainFrame = WindowManager.getDefault().getMainWindow();
165  }
166  });
167  }
168 
172  public enum CaseType {
173 
174  SINGLE_USER_CASE("Single-user case"), //NON-NLS
175  MULTI_USER_CASE("Multi-user case"); //NON-NLS
176 
177  private final String typeName;
178 
186  public static CaseType fromString(String typeName) {
187  if (typeName != null) {
188  for (CaseType c : CaseType.values()) {
189  if (typeName.equalsIgnoreCase(c.toString())) {
190  return c;
191  }
192  }
193  }
194  return null;
195  }
196 
202  @Override
203  public String toString() {
204  return typeName;
205  }
206 
212  @Messages({
213  "Case_caseType_singleUser=Single-user case",
214  "Case_caseType_multiUser=Multi-user case"
215  })
217  if (fromString(typeName) == SINGLE_USER_CASE) {
218  return Bundle.Case_caseType_singleUser();
219  } else {
220  return Bundle.Case_caseType_multiUser();
221  }
222  }
223 
229  private CaseType(String typeName) {
230  this.typeName = typeName;
231  }
232 
243  @Deprecated
244  public boolean equalsName(String otherTypeName) {
245  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
246  }
247 
248  };
249 
254  public enum Events {
255 
263  @Deprecated
272  @Deprecated
281  @Deprecated
391 
392  };
393 
400  public static void addPropertyChangeListener(PropertyChangeListener listener) {
401  addEventSubscriber(Stream.of(Events.values())
402  .map(Events::toString)
403  .collect(Collectors.toSet()), listener);
404  }
405 
412  public static void removePropertyChangeListener(PropertyChangeListener listener) {
413  removeEventSubscriber(Stream.of(Events.values())
414  .map(Events::toString)
415  .collect(Collectors.toSet()), listener);
416  }
417 
426  @Deprecated
427  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
428  eventPublisher.addSubscriber(eventNames, subscriber);
429  }
430 
437  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
438  eventTypes.forEach((Events event) -> {
439  eventPublisher.addSubscriber(event.toString(), subscriber);
440  });
441  }
442 
451  @Deprecated
452  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
453  eventPublisher.addSubscriber(eventName, subscriber);
454  }
455 
462  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
463  eventPublisher.removeSubscriber(eventName, subscriber);
464  }
465 
472  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
473  eventPublisher.removeSubscriber(eventNames, subscriber);
474  }
475 
482  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
483  eventTypes.forEach((Events event) -> {
484  eventPublisher.removeSubscriber(event.toString(), subscriber);
485  });
486  }
487 
496  public static boolean isValidName(String caseName) {
497  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
498  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
499  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
500  }
501 
526  @Deprecated
527  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
528  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
529  }
530 
550  @Messages({
551  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
552  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
553  })
554  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
555  if (caseDetails.getCaseDisplayName().isEmpty()) {
556  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
557  }
558  if (caseDir.isEmpty()) {
559  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
560  }
561  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
562  }
563 
577  @Messages({
578  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
579  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
580  })
581  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
583  try {
584  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
585  } catch (CaseMetadataException ex) {
586  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
587  }
589  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
590  }
591  openAsCurrentCase(new Case(metadata), false);
592  }
593 
599  public static boolean isCaseOpen() {
600  return currentCase != null;
601  }
602 
610  public static Case getCurrentCase() {
611  try {
612  return getCurrentCaseThrows();
613  } catch (NoCurrentCaseException ex) {
614  /*
615  * Throw a runtime exception, since this is a programming error.
616  */
617  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
618  }
619  }
620 
639  Case openCase = currentCase;
640  if (openCase == null) {
641  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
642  } else {
643  return openCase;
644  }
645  }
646 
655  @Messages({
656  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
657  "Case.progressIndicatorTitle.closingCase=Closing Case"
658  })
659  public static void closeCurrentCase() throws CaseActionException {
660  synchronized (caseActionSerializationLock) {
661  if (null == currentCase) {
662  return;
663  }
664  Case closedCase = currentCase;
665  try {
666  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
667  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
668  currentCase = null;
669  closedCase.close();
670  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
671  } catch (CaseActionException ex) {
672  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
673  throw ex;
674  } finally {
677  }
678  }
679  }
680  }
681 
690  public static void deleteCurrentCase() throws CaseActionException {
691  synchronized (caseActionSerializationLock) {
692  if (null == currentCase) {
693  return;
694  }
695  CaseMetadata metadata = currentCase.getMetadata();
697  deleteCase(metadata);
698  }
699  }
700 
712  @Messages({
713  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
714  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
715  "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
716  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service.",
717  "Case.exceptionMessage.failedToDeleteCoordinationServiceNodes=Failed to delete the coordination service nodes for the case."
718  })
719  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
720  StopWatch stopWatch = new StopWatch();
721  stopWatch.start();
722  synchronized (caseActionSerializationLock) {
723  if (null != currentCase) {
724  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
725  }
726  }
727  stopWatch.stop();
728  logger.log(Level.INFO, String.format("Used %d s to acquire caseActionSerializationLock (Java monitor in Case class) for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
729 
730  /*
731  * Set up either a GUI progress indicator without a cancel button (can't
732  * cancel deleting a case) or a logging progress indicator.
733  */
734  ProgressIndicator progressIndicator;
736  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
737  } else {
738  progressIndicator = new LoggingProgressIndicator();
739  }
740  progressIndicator.start(Bundle.Case_progressMessage_preparing());
741  try {
742  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
743  deleteCase(metadata, progressIndicator);
744  } else {
745  /*
746  * First, acquire an exclusive case directory lock. The case
747  * cannot be deleted if another node has it open.
748  */
749  progressIndicator.progress(Bundle.Case_progressMessage_checkingForOtherUser());
750  stopWatch.reset();
751  stopWatch.start();
753  stopWatch.stop();
754  logger.log(Level.INFO, String.format("Used %d s to acquire case directory coordination service lock for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
755  if (dirLock != null) {
756  deleteCase(metadata, progressIndicator);
757  } else {
758  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
759  }
760  } catch (CoordinationServiceException ex) {
761  stopWatch.stop();
762  logger.log(Level.INFO, String.format("Used %d s to fail to acquire case directory coordination service lock for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
763  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToDeleteCoordinationServiceNodes(), ex);
764  }
765  try {
766  deleteCoordinationServiceNodes(metadata, progressIndicator);
767  } catch (CoordinationServiceException ex) {
768  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
769  }
770  }
771  } finally {
772  progressIndicator.finish();
773  }
774  }
775 
786  @Messages({
787  "Case.progressMessage.deletingCoordinationServiceNodes=Deleting coordination service nodes..."
788  })
789  static void deleteCoordinationServiceNodes(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CoordinationServiceException {
790  progressIndicator.progress(Bundle.Case_progressMessage_deletingCoordinationServiceNodes());
791  CoordinationService coordinationService;
792  coordinationService = CoordinationService.getInstance();
793  String resourcesLockNodePath = metadata.getCaseDirectory() + "_resources";
794  try {
795  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
796  } catch (CoordinationServiceException ex) {
797  /*
798  * Log but do not notify the user.
799  */
800  logger.log(Level.SEVERE, String.format("Failed to delete resources lock coordination service node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
801  }
802  String caseDirectoryLockNodePath = metadata.getCaseDirectory();
803  try {
804  coordinationService.deleteNode(CategoryNode.CASES, caseDirectoryLockNodePath);
805  } catch (CoordinationServiceException ex) {
806  /*
807  * Log but do not notify the user.
808  */
809  logger.log(Level.SEVERE, String.format("Failed to delete case directory lock coordination service node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
810  }
811  }
812 
823  @Messages({
824  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
825  })
826  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
827  synchronized (caseActionSerializationLock) {
828  if (null != currentCase) {
829  try {
831  } catch (CaseActionException ex) {
832  /*
833  * Notify the user and continue (the error has already been
834  * logged in closeCurrentCase.
835  */
836  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
837  }
838  }
839  try {
840  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
841  newCurrentCase.open(isNewCase);
842  currentCase = newCurrentCase;
843  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
845  updateGUIForCaseOpened(newCurrentCase);
846  }
847  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
848  } catch (CaseActionCancelledException ex) {
849  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
850  throw ex;
851  } catch (CaseActionException ex) {
852  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
853  throw ex;
854  }
855  }
856  }
857 
866  private static String displayNameToUniqueName(String caseDisplayName) {
867  /*
868  * Replace all non-ASCII characters.
869  */
870  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
871 
872  /*
873  * Replace all control characters.
874  */
875  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
876 
877  /*
878  * Replace /, \, :, ?, space, ' ".
879  */
880  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
881 
882  /*
883  * Make it all lowercase.
884  */
885  uniqueCaseName = uniqueCaseName.toLowerCase();
886 
887  /*
888  * Add a time stamp for uniqueness.
889  */
890  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
891  Date date = new Date();
892  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
893 
894  return uniqueCaseName;
895  }
896 
906  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
907  /*
908  * Check the case directory path and permissions. The case directory may
909  * already exist.
910  */
911  File caseDir = new File(caseDirPath);
912  if (caseDir.exists()) {
913  if (caseDir.isFile()) {
914  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
915  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
916  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
917  }
918  }
919 
920  /*
921  * Create the case directory, if it does not already exist.
922  */
923  if (!caseDir.mkdirs()) {
924  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
925  }
926 
927  /*
928  * Create the subdirectories of the case directory, if they do not
929  * already exist. Note that multi-user cases get an extra layer of
930  * subdirectories, one subdirectory per application host machine.
931  */
932  String hostPathComponent = "";
933  if (caseType == CaseType.MULTI_USER_CASE) {
934  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
935  }
936 
937  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
938  if (!exportDir.toFile().mkdirs()) {
939  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
940  }
941 
942  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
943  if (!logsDir.toFile().mkdirs()) {
944  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
945  }
946 
947  Path tempDir = Paths.get(caseDirPath, hostPathComponent, TEMP_FOLDER);
948  if (!tempDir.toFile().mkdirs()) {
949  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", tempDir));
950  }
951 
952  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
953  if (!cacheDir.toFile().mkdirs()) {
954  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
955  }
956 
957  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
958  if (!moduleOutputDir.toFile().mkdirs()) {
959  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
960  }
961 
962  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
963  if (!reportsDir.toFile().mkdirs()) {
964  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
965  }
966  }
967 
975  static Map<Long, String> getImagePaths(SleuthkitCase db) {
976  Map<Long, String> imgPaths = new HashMap<>();
977  try {
978  Map<Long, List<String>> imgPathsList = db.getImagePaths();
979  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
980  if (entry.getValue().size() > 0) {
981  imgPaths.put(entry.getKey(), entry.getValue().get(0));
982  }
983  }
984  } catch (TskCoreException ex) {
985  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
986  }
987  return imgPaths;
988  }
989 
1006  @Messages({
1007  "Case.progressMessage.deletingTextIndex=Deleting text index...",
1008  "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
1009  "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
1010  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
1011  })
1012  private static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
1013  StopWatch stopWatch = new StopWatch();
1014  boolean errorsOccurred = false;
1015  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1016  /*
1017  * Delete the case database from the database server.
1018  */
1019  stopWatch.start();
1020  try {
1021  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
1022  CaseDbConnectionInfo db;
1024  Class.forName("org.postgresql.Driver"); //NON-NLS
1025  try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
1026  Statement statement = connection.createStatement();) {
1027  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
1028  statement.execute(deleteCommand);
1029  stopWatch.stop();
1030  logger.log(Level.INFO, String.format("Used %d s to delete case database for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1031  }
1032  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
1033  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);
1034  errorsOccurred = true;
1035  stopWatch.stop();
1036  logger.log(Level.INFO, String.format("Used %d s to fail delete case database for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1037  }
1038  }
1039 
1040  /*
1041  * Delete the text index.
1042  */
1043  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
1044  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
1045  try {
1046  stopWatch.reset();
1047  stopWatch.start();
1048  searchService.deleteTextIndex(metadata);
1049  stopWatch.stop();
1050  logger.log(Level.INFO, String.format("Used %d s to delete text index for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1051  } catch (KeywordSearchServiceException ex) {
1052  logger.log(Level.SEVERE, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex);
1053  errorsOccurred = true;
1054  stopWatch.stop();
1055  logger.log(Level.INFO, String.format("Used %d s to fail to delete text index for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1056  }
1057  }
1058 
1059  /*
1060  * Delete the case directory.
1061  */
1062  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
1063  stopWatch.reset();
1064  stopWatch.start();
1065  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
1066  logger.log(Level.SEVERE, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1067  errorsOccurred = true;
1068  stopWatch.stop();
1069  logger.log(Level.INFO, String.format("Used %d s to fail to delete case directory for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1070  } else {
1071  stopWatch.stop();
1072  logger.log(Level.INFO, String.format("Used %d s to delete case directory for %s (%s) in %s", stopWatch.getElapsedTimeSecs(), metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()));
1073  }
1074 
1075  /*
1076  * If running in a GUI, remove the case from the Recent Cases menu
1077  */
1079  SwingUtilities.invokeLater(() -> {
1080  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
1081  });
1082  }
1083 
1084  if (errorsOccurred) {
1085  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
1086  }
1087  }
1088 
1099  @Messages({
1100  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1101  })
1102  private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException {
1103  try {
1104  String resourcesNodeName = caseDir + "_resources";
1105  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
1106  return lock;
1107  } catch (InterruptedException ex) {
1108  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1109  } catch (CoordinationServiceException ex) {
1110  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1111  }
1112  }
1113 
1114  private static String getNameForTitle() {
1115  //Method should become unnecessary once technical debt story 3334 is done.
1116  if (UserPreferences.getAppName().equals(Version.getName())) {
1117  //Available version number is version number for this application
1118  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1119  } else {
1120  return UserPreferences.getAppName();
1121  }
1122  }
1123 
1127  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1129  SwingUtilities.invokeLater(() -> {
1130  /*
1131  * If the case database was upgraded for a new schema and a
1132  * backup database was created, notify the user.
1133  */
1134  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1135  String backupDbPath = caseDb.getBackupDatabasePath();
1136  if (null != backupDbPath) {
1137  JOptionPane.showMessageDialog(
1138  mainFrame,
1139  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1140  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1141  JOptionPane.INFORMATION_MESSAGE);
1142  }
1143 
1144  /*
1145  * Look for the files for the data sources listed in the case
1146  * database and give the user the opportunity to locate any that
1147  * are missing.
1148  */
1149  Map<Long, String> imgPaths = getImagePaths(caseDb);
1150  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1151  long obj_id = entry.getKey();
1152  String path = entry.getValue();
1153  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1154  if (!fileExists) {
1155  int response = JOptionPane.showConfirmDialog(
1156  mainFrame,
1157  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1158  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1159  JOptionPane.YES_NO_OPTION);
1160  if (response == JOptionPane.YES_OPTION) {
1161  MissingImageDialog.makeDialog(obj_id, caseDb);
1162  } else {
1163  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1164 
1165  }
1166  }
1167  }
1168 
1169  /*
1170  * Enable the case-specific actions.
1171  */
1172  CallableSystemAction.get(AddImageAction.class).setEnabled(true);
1173  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1174  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1175  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1176  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
1177  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1178  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1179  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1180  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1181 
1182  /*
1183  * Add the case to the recent cases tracker that supplies a list
1184  * of recent cases to the recent cases menu item and the
1185  * open/create case dialog.
1186  */
1187  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1188 
1189  /*
1190  * Open the top components (windows within the main application
1191  * window).
1192  *
1193  * Note: If the core windows are not opened here, they will be
1194  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1195  * method on a DATA_SOURCE_ADDED event.
1196  */
1197  if (newCurrentCase.hasData()) {
1199  }
1200 
1201  /*
1202  * Reset the main window title to:
1203  *
1204  * [curent case display name] - [application name].
1205  */
1206  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1207  });
1208  }
1209  }
1210 
1211  /*
1212  * Update the GUI to to reflect the lack of a current case.
1213  */
1214  private static void updateGUIForCaseClosed() {
1216  SwingUtilities.invokeLater(() -> {
1217  /*
1218  * Close the top components (windows within the main application
1219  * window).
1220  */
1222 
1223  /*
1224  * Disable the case-specific menu items.
1225  */
1226  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1227  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1228  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1229  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1230  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1231  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1232  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1233  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1234  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1235 
1236  /*
1237  * Clear the notifications in the notfier component in the lower
1238  * right hand corner of the main application window.
1239  */
1241 
1242  /*
1243  * Reset the main window title to be just the application name,
1244  * instead of [curent case display name] - [application name].
1245  */
1246  mainFrame.setTitle(getNameForTitle());
1247  });
1248  }
1249  }
1250 
1254  private static void clearTempSubDir(String tempSubDirPath) {
1255  File tempFolder = new File(tempSubDirPath);
1256  if (tempFolder.isDirectory()) {
1257  File[] files = tempFolder.listFiles();
1258  if (files.length > 0) {
1259  for (File file : files) {
1260  if (file.isDirectory()) {
1261  FileUtil.deleteDir(file);
1262  } else {
1263  file.delete();
1264  }
1265  }
1266  }
1267  }
1268  }
1269 
1275  public SleuthkitCase getSleuthkitCase() {
1276  return this.caseDb;
1277  }
1278 
1285  return caseServices;
1286  }
1287 
1294  return metadata.getCaseType();
1295  }
1296 
1302  public String getCreatedDate() {
1303  return metadata.getCreatedDate();
1304  }
1305 
1311  public String getName() {
1312  return metadata.getCaseName();
1313  }
1314 
1320  public String getDisplayName() {
1321  return metadata.getCaseDisplayName();
1322  }
1323 
1329  public String getNumber() {
1330  return metadata.getCaseNumber();
1331  }
1332 
1338  public String getExaminer() {
1339  return metadata.getExaminer();
1340  }
1341 
1347  public String getExaminerPhone() {
1348  return metadata.getExaminerPhone();
1349  }
1350 
1356  public String getExaminerEmail() {
1357  return metadata.getExaminerEmail();
1358  }
1359 
1365  public String getCaseNotes() {
1366  return metadata.getCaseNotes();
1367  }
1368 
1374  public String getCaseDirectory() {
1375  return metadata.getCaseDirectory();
1376  }
1377 
1386  public String getOutputDirectory() {
1387  String caseDirectory = getCaseDirectory();
1388  Path hostPath;
1389  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1390  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1391  } else {
1392  hostPath = Paths.get(caseDirectory);
1393  }
1394  if (!hostPath.toFile().exists()) {
1395  hostPath.toFile().mkdirs();
1396  }
1397  return hostPath.toString();
1398  }
1399 
1406  public String getTempDirectory() {
1407  return getOrCreateSubdirectory(TEMP_FOLDER);
1408  }
1409 
1416  public String getCacheDirectory() {
1417  return getOrCreateSubdirectory(CACHE_FOLDER);
1418  }
1419 
1426  public String getExportDirectory() {
1427  return getOrCreateSubdirectory(EXPORT_FOLDER);
1428  }
1429 
1436  public String getLogDirectoryPath() {
1437  return getOrCreateSubdirectory(LOG_FOLDER);
1438  }
1439 
1446  public String getReportDirectory() {
1447  return getOrCreateSubdirectory(REPORTS_FOLDER);
1448  }
1449 
1456  public String getConfigDirectory() {
1457  return getOrCreateSubdirectory(CONFIG_FOLDER);
1458  }
1459 
1466  public String getModuleDirectory() {
1467  return getOrCreateSubdirectory(MODULE_FOLDER);
1468  }
1469 
1478  Path path = Paths.get(getModuleDirectory());
1480  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1481  } else {
1482  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1483  }
1484  }
1485 
1495  public List<Content> getDataSources() throws TskCoreException {
1496  List<Content> list = caseDb.getRootObjects();
1497  hasDataSources = (list.size() > 0);
1498  return list;
1499  }
1500 
1506  public Set<TimeZone> getTimeZones() {
1507  Set<TimeZone> timezones = new HashSet<>();
1508  try {
1509  for (Content c : getDataSources()) {
1510  final Content dataSource = c.getDataSource();
1511  if ((dataSource != null) && (dataSource instanceof Image)) {
1512  Image image = (Image) dataSource;
1513  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1514  }
1515  }
1516  } catch (TskCoreException ex) {
1517  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1518  }
1519  return timezones;
1520  }
1521 
1528  public String getTextIndexName() {
1529  return getMetadata().getTextIndexName();
1530  }
1531 
1538  public boolean hasData() {
1539  if (!hasDataSources) {
1540  try {
1541  hasDataSources = (getDataSources().size() > 0);
1542  } catch (TskCoreException ex) {
1543  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1544  }
1545  }
1546  return hasDataSources;
1547  }
1548 
1559  public void notifyAddingDataSource(UUID eventId) {
1560  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1561  }
1562 
1573  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1574  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1575  }
1576 
1588  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1589  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1590  }
1591 
1601  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1602  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1603  }
1604 
1612  public void notifyContentTagAdded(ContentTag newTag) {
1613  eventPublisher.publish(new ContentTagAddedEvent(newTag));
1614  }
1615 
1623  public void notifyContentTagDeleted(ContentTag deletedTag) {
1624  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1625  }
1626 
1634  public void notifyTagDefinitionChanged(String changedTagName) {
1635  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1636  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1637  }
1638 
1649  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1650  try {
1651  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1652  } catch (NoCurrentCaseException ex) {
1653  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1654  }
1655  }
1656 
1664  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1665  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
1666  }
1667 
1675  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1676  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1677  }
1678 
1690  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1691  addReport(localPath, srcModuleName, reportName, null);
1692  }
1693 
1708  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1709  String normalizedLocalPath;
1710  try {
1711  if (localPath.toLowerCase().contains("http:")) {
1712  normalizedLocalPath = localPath;
1713  } else {
1714  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1715  }
1716  } catch (InvalidPathException ex) {
1717  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1718  throw new TskCoreException(errorMsg, ex);
1719  }
1720  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1721  eventPublisher.publish(new ReportAddedEvent(report));
1722  return report;
1723  }
1724 
1733  public List<Report> getAllReports() throws TskCoreException {
1734  return this.caseDb.getAllReports();
1735  }
1736 
1745  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1746  for (Report report : reports) {
1747  this.caseDb.deleteReport(report);
1748  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1749  }
1750  }
1751 
1757  CaseMetadata getMetadata() {
1758  return metadata;
1759  }
1760 
1768  @Messages({
1769  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1770  })
1771  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1772  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1773  try {
1774  metadata.setCaseDetails(caseDetails);
1775  } catch (CaseMetadataException ex) {
1776  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1777  }
1778  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1779  try {
1780  CoordinationService coordinationService = CoordinationService.getInstance();
1781  CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
1782  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
1783  coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
1784  } catch (CoordinationServiceException | InterruptedException | IOException ex) {
1785  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
1786  }
1787  }
1788  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1789  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1790  }
1791  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1792  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1793  }
1794  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1795  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1796  }
1797  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1798  if (RuntimeProperties.runningWithGUI()) {
1799  SwingUtilities.invokeLater(() -> {
1800  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1801  try {
1802  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1803  } catch (Exception ex) {
1804  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1805  }
1806  });
1807  }
1808  }
1809 
1822  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1823  metadata = new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails);
1824  }
1825 
1831  private Case(CaseMetadata caseMetaData) {
1832  metadata = caseMetaData;
1833  }
1834 
1850  @Messages({
1851  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1852  "Case.progressIndicatorTitle.openingCase=Opening Case",
1853  "Case.progressIndicatorCancelButton.label=Cancel",
1854  "Case.progressMessage.preparing=Preparing...",
1855  "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1856  "Case.progressMessage.cancelling=Cancelling...",
1857  "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1858  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1859  })
1860  private void open(boolean isNewCase) throws CaseActionException {
1861  /*
1862  * Create and start either a GUI progress indicator with a Cancel button
1863  * or a logging progress indicator.
1864  */
1865  CancelButtonListener cancelButtonListener = null;
1866  ProgressIndicator progressIndicator;
1868  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1869  String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1870  progressIndicator = new ModalDialogProgressIndicator(
1871  mainFrame,
1872  progressIndicatorTitle,
1873  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1874  Bundle.Case_progressIndicatorCancelButton_label(),
1875  cancelButtonListener);
1876  } else {
1877  progressIndicator = new LoggingProgressIndicator();
1878  }
1879  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1880 
1881  /*
1882  * Creating/opening a case is always done by creating a task running in
1883  * the same non-UI thread that will be used to close the case, so a
1884  * single-threaded executor service is created here and saved as case
1885  * state (must be volatile for cancellation to work).
1886  *
1887  * --- If the case is a single-user case, this supports cancelling
1888  * opening of the case by cancelling the task.
1889  *
1890  * --- If the case is a multi-user case, this still supports
1891  * cancellation, but it also makes it possible for the shared case
1892  * directory lock held as long as the case is open to be released in the
1893  * same thread in which it was acquired, as is required by the
1894  * coordination service.
1895  */
1896  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1897  caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1898  Future<Void> future = caseLockingExecutor.submit(() -> {
1899  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1900  open(isNewCase, progressIndicator);
1901  } else {
1902  /*
1903  * First, acquire a shared case directory lock that will be held
1904  * as long as this node has this case open. This will prevent
1905  * deletion of the case by another node. Next, acquire an
1906  * exclusive case resources lock to ensure only one node at a
1907  * time can create/open/upgrade/close the case resources.
1908  */
1909  progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1912  if (null == resourcesLock) {
1913  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
1914  }
1915  open(isNewCase, progressIndicator);
1916  } catch (CaseActionException ex) {
1917  releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
1918  throw ex;
1919  }
1920  }
1921  return null;
1922  });
1923  if (null != cancelButtonListener) {
1924  cancelButtonListener.setCaseActionFuture(future);
1925  }
1926 
1927  /*
1928  * Wait for the case creation/opening task to finish.
1929  */
1930  try {
1931  future.get();
1932  } catch (InterruptedException discarded) {
1933  /*
1934  * The thread this method is running in has been interrupted. Cancel
1935  * the create/open task, wait for it to finish, and shut down the
1936  * executor. This can be done safely because if the task is
1937  * completed with a cancellation condition, the case will have been
1938  * closed and the case directory lock released will have been
1939  * released.
1940  */
1941  if (null != cancelButtonListener) {
1942  cancelButtonListener.actionPerformed(null);
1943  } else {
1944  future.cancel(true);
1945  }
1946  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1947  } catch (CancellationException discarded) {
1948  /*
1949  * The create/open task has been cancelled. Wait for it to finish,
1950  * and shut down the executor. This can be done safely because if
1951  * the task is completed with a cancellation condition, the case
1952  * will have been closed and the case directory lock released will
1953  * have been released.
1954  */
1955  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1956  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
1957  } catch (ExecutionException ex) {
1958  /*
1959  * The create/open task has thrown an exception. Wait for it to
1960  * finish, and shut down the executor. This can be done safely
1961  * because if the task is completed with an execution condition, the
1962  * case will have been closed and the case directory lock released
1963  * will have been released.
1964  */
1965  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
1966  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1967  } finally {
1968  progressIndicator.finish();
1969  }
1970  }
1971 
1983  private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException {
1984  try {
1986  createCaseDirectoryIfDoesNotExist(progressIndicator);
1988  switchLoggingToCaseLogsDirectory(progressIndicator);
1990  if (isNewCase) {
1991  saveCaseMetadataToFile(progressIndicator);
1992  }
1994  if (isNewCase) {
1995  createCaseNodeData(progressIndicator);
1996  } else {
1997  updateCaseNodeData(progressIndicator);
1998  }
2000  if (!isNewCase) {
2001  deleteTempfilesFromCaseDirectory(progressIndicator);
2002  }
2004  if (isNewCase) {
2005  createCaseDatabase(progressIndicator);
2006  } else {
2007  openCaseDataBase(progressIndicator);
2008  }
2010  openCaseLevelServices(progressIndicator);
2012  openAppServiceCaseResources(progressIndicator);
2014  openCommunicationChannels(progressIndicator);
2015 
2016  } catch (CaseActionException ex) {
2017  /*
2018  * Cancellation or failure. Clean up by calling the close method.
2019  * The sleep is a little hack to clear the interrupted flag for this
2020  * thread if this is a cancellation scenario, so that the clean up
2021  * can run to completion in the current thread.
2022  */
2023  try {
2024  Thread.sleep(1);
2025  } catch (InterruptedException discarded) {
2026  }
2027  close(progressIndicator);
2028  throw ex;
2029  }
2030  }
2031 
2042  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2043 
2044  if (portableCaseFolder.exists()) {
2045  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2046  }
2047  if (!portableCaseFolder.mkdirs()) {
2048  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2049  }
2050 
2051  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2053  try {
2054  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2055  caseName, details, metadata);
2056  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2057  } catch (CaseMetadataException ex) {
2058  throw new TskCoreException("Error creating case metadata", ex);
2059  }
2060 
2061  // Create the Sleuthkit case
2062  SleuthkitCase portableSleuthkitCase;
2063  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2064  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2065 
2066  return portableSleuthkitCase;
2067  }
2068 
2079  if (Thread.currentThread().isInterrupted()) {
2080  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
2081  }
2082  }
2083 
2097  @Messages({
2098  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2099  })
2100  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2101  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2102  if (new File(metadata.getCaseDirectory()).exists() == false) {
2103  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2104  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2105  }
2106  }
2107 
2114  @Messages({
2115  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2116  })
2117  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2118  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2120  }
2121 
2133  @Messages({
2134  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2135  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2136  })
2137  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2138  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2139  try {
2140  this.metadata.writeToFile();
2141  } catch (CaseMetadataException ex) {
2142  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2143  }
2144  }
2145 
2157  @Messages({
2158  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2159  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2160  })
2161  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2163  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2164  try {
2165  CoordinationService coordinationService = CoordinationService.getInstance();
2166  CaseNodeData nodeData = new CaseNodeData(metadata);
2167  coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
2168  } catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) {
2169  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2170  }
2171  }
2172  }
2173 
2183  @Messages({
2184  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2185  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2186  })
2187  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2189  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2191  try {
2192  CoordinationService coordinationService = CoordinationService.getInstance();
2193  CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
2194  nodeData.setLastAccessDate(new Date());
2195  coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
2196  } catch (CoordinationServiceException | InterruptedException | IOException ex) {
2197  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2198  }
2199  }
2200  }
2201  }
2202 
2208  @Messages({
2209  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2210  })
2211  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2212  /*
2213  * Clear the temp subdirectory of the case directory.
2214  */
2215  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2217  }
2218 
2230  @Messages({
2231  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2232  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2233  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2234  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2235  })
2236  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2237  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2238  try {
2239  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2240  /*
2241  * For single-user cases, the case database is a SQLite database
2242  * with a standard name, physically located in the case
2243  * directory.
2244  */
2245  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2246  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2247  } else {
2248  /*
2249  * For multi-user cases, the case database is a PostgreSQL
2250  * database with a name derived from the case display name,
2251  * physically located on the PostgreSQL database server.
2252  */
2253  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2254  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2255  }
2256  } catch (TskCoreException ex) {
2257  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2258  } catch (UserPreferencesException ex) {
2259  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2260  } catch (CaseMetadataException ex) {
2261  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2262  }
2263  }
2264 
2276  @Messages({
2277  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2278  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2279  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2280  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2281  })
2282  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2283  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2284  try {
2285  String databaseName = metadata.getCaseDatabaseName();
2286  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2287  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
2289  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2290  } else {
2291  throw new CaseActionException(Case_open_exception_multiUserCaseNotEnabled());
2292  }
2293  } catch (TskUnsupportedSchemaVersionException ex) {
2294  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2295  } catch (UserPreferencesException ex) {
2296  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2297  } catch (TskCoreException ex) {
2298  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2299  }
2300  }
2301 
2308  @Messages({
2309  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2310  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2311  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2312  this.caseServices = new Services(caseDb);
2313  }
2314 
2326  @NbBundle.Messages({
2327  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2328  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2329  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2330  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2331  })
2332  private void openAppServiceCaseResources(ProgressIndicator progressIndicator) throws CaseActionException {
2333  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2334  /*
2335  * Each service gets its own independently cancellable/interruptible
2336  * task, running in a named thread managed by an executor service, with
2337  * its own progress indicator. This allows for cancellation of the
2338  * opening of case resources for individual services. It also makes it
2339  * possible to ensure that each service task completes before the next
2340  * one starts by awaiting termination of the executor service.
2341  */
2342  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2343  /*
2344  * Create a progress indicator for the task and start the task. If
2345  * running with a GUI, the progress indicator will be a dialog box
2346  * with a Cancel button.
2347  */
2348  CancelButtonListener cancelButtonListener = null;
2349  ProgressIndicator appServiceProgressIndicator;
2351  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2352  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2353  mainFrame,
2354  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2355  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2356  Bundle.Case_progressIndicatorCancelButton_label(),
2357  cancelButtonListener);
2358  } else {
2359  appServiceProgressIndicator = new LoggingProgressIndicator();
2360  }
2361  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2362  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator);
2363  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2364  threadNameSuffix = threadNameSuffix.toLowerCase();
2365  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2366  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2367  Future<Void> future = executor.submit(() -> {
2368  service.openCaseResources(context);
2369  return null;
2370  });
2371  if (null != cancelButtonListener) {
2372  cancelButtonListener.setCaseContext(context);
2373  cancelButtonListener.setCaseActionFuture(future);
2374  }
2375 
2376  /*
2377  * Wait for the task to either be completed or
2378  * cancelled/interrupted, or for the opening of the case to be
2379  * cancelled.
2380  */
2381  try {
2382  future.get();
2383  } catch (InterruptedException discarded) {
2384  /*
2385  * The parent create/open case task has been cancelled.
2386  */
2387  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()));
2388  future.cancel(true);
2389  } catch (CancellationException discarded) {
2390  /*
2391  * The opening of case resources by the application service has
2392  * been cancelled, so the executor service has thrown. Note that
2393  * there is no guarantee the task itself has responded to the
2394  * cancellation request yet.
2395  */
2396  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()));
2397  } catch (ExecutionException ex) {
2398  /*
2399  * An exception was thrown while executing the task. The
2400  * case-specific application service resources are not
2401  * essential. Log an error and notify the user if running the
2402  * desktop GUI, but do not throw.
2403  */
2404  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2406  SwingUtilities.invokeLater(() -> {
2407  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2408  });
2409  }
2410  } finally {
2411  /*
2412  * Shut down the executor service and wait for it to finish.
2413  * This ensures that the task has finished. Without this, it
2414  * would be possible to start the next task before the current
2415  * task responded to a cancellation request.
2416  */
2418  appServiceProgressIndicator.finish();
2419  }
2421  }
2422  }
2423 
2435  @Messages({
2436  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2437  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2438  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2439  })
2440  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2441  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2442  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2443  try {
2444  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2446  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2447  } catch (AutopsyEventException ex) {
2448  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2449  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2450  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2451  }
2452  }
2453  }
2454 
2463  private void close() throws CaseActionException {
2464  /*
2465  * Set up either a GUI progress indicator without a Cancel button or a
2466  * logging progress indicator.
2467  */
2468  ProgressIndicator progressIndicator;
2470  progressIndicator = new ModalDialogProgressIndicator(
2471  mainFrame,
2472  Bundle.Case_progressIndicatorTitle_closingCase());
2473  } else {
2474  progressIndicator = new LoggingProgressIndicator();
2475  }
2476  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2477 
2478  /*
2479  * Closing a case is always done in the same non-UI thread that
2480  * opened/created the case. If the case is a multi-user case, this
2481  * ensures that case directory lock that is held as long as the case is
2482  * open is released in the same thread in which it was acquired, as is
2483  * required by the coordination service.
2484  */
2485  Future<Void> future = caseLockingExecutor.submit(() -> {
2486  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2487  close(progressIndicator);
2488  } else {
2489  /*
2490  * Acquire an exclusive case resources lock to ensure only one
2491  * node at a time can create/open/upgrade/close the case
2492  * resources.
2493  */
2494  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2496  if (null == resourcesLock) {
2497  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2498  }
2499  close(progressIndicator);
2500  } finally {
2501  /*
2502  * Always release the case directory lock that was acquired
2503  * when the case was opened.
2504  */
2506  }
2507  }
2508  return null;
2509  });
2510 
2511  try {
2512  future.get();
2513  } catch (InterruptedException | CancellationException unused) {
2514  /*
2515  * The wait has been interrupted by interrupting the thread running
2516  * this method. Not allowing cancellation of case closing, so ignore
2517  * the interrupt. Likewsie, cancellation of the case closing task is
2518  * not supported.
2519  */
2520  } catch (ExecutionException ex) {
2521  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2522  } finally {
2523  ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
2524  progressIndicator.finish();
2525  }
2526  }
2527 
2533  @Messages({
2534  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2535  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2536  "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2537  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2538  })
2539  private void close(ProgressIndicator progressIndicator) {
2541 
2542  /*
2543  * Stop sending/receiving case events to and from other nodes if this is
2544  * a multi-user case.
2545  */
2546  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2547  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2548  if (null != collaborationMonitor) {
2549  collaborationMonitor.shutdown();
2550  }
2551  eventPublisher.closeRemoteEventChannel();
2552  }
2553 
2554  /*
2555  * Allow all registered application services providers to close
2556  * resources related to the case.
2557  */
2558  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2560 
2561  /*
2562  * Close the case-level services.
2563  */
2564  if (null != caseServices) {
2565  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2566  try {
2567  this.caseServices.close();
2568  } catch (IOException ex) {
2569  logger.log(Level.SEVERE, String.format("Error closing internal case services for %s at %s", this.getName(), this.getCaseDirectory()), ex);
2570  }
2571  }
2572 
2573  /*
2574  * Close the case database
2575  */
2576  if (null != caseDb) {
2577  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2578  caseDb.close();
2579  }
2580 
2581  /*
2582  * Switch the log directory.
2583  */
2584  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2586  }
2587 
2592  @Messages({
2593  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2594  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2595  })
2597  /*
2598  * Each service gets its own independently cancellable task, and thus
2599  * its own task progress indicator.
2600  */
2601  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
2602  ProgressIndicator progressIndicator;
2604  progressIndicator = new ModalDialogProgressIndicator(
2605  mainFrame,
2606  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2607  } else {
2608  progressIndicator = new LoggingProgressIndicator();
2609  }
2610  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2611  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2612  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2613  threadNameSuffix = threadNameSuffix.toLowerCase();
2614  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2615  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2616  Future<Void> future = executor.submit(() -> {
2617  service.closeCaseResources(context);
2618  return null;
2619  });
2620  try {
2621  future.get();
2622  } catch (InterruptedException ex) {
2623  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2624  } catch (CancellationException ex) {
2625  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2626  } catch (ExecutionException ex) {
2627  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2629  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2630  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2631  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2632  }
2633  } finally {
2635  progressIndicator.finish();
2636  }
2637  }
2638  }
2639 
2648  @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory"})
2649  private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException {
2650  try {
2651  caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
2652  if (null == caseDirLock) {
2653  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
2654  }
2655  } catch (InterruptedException | CoordinationServiceException ex) {
2656  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2657  }
2658  }
2659 
2665  private void releaseSharedCaseDirLock(String caseDir) {
2666  if (caseDirLock != null) {
2667  try {
2668  caseDirLock.release();
2669  caseDirLock = null;
2671  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2672  }
2673  }
2674  }
2675 
2682  private String getOrCreateSubdirectory(String subDirectoryName) {
2683  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2684  if (!subDirectory.exists()) {
2685  subDirectory.mkdirs();
2686  }
2687  return subDirectory.toString();
2688 
2689  }
2690 
2695  @ThreadSafe
2696  private final static class CancelButtonListener implements ActionListener {
2697 
2698  private final String cancellationMessage;
2699  @GuardedBy("this")
2700  private boolean cancelRequested;
2701  @GuardedBy("this")
2703  @GuardedBy("this")
2704  private Future<?> caseActionFuture;
2705 
2714  private CancelButtonListener(String cancellationMessage) {
2715  this.cancellationMessage = cancellationMessage;
2716  }
2717 
2723  private synchronized void setCaseContext(CaseContext caseContext) {
2724  this.caseContext = caseContext;
2725  /*
2726  * If the cancel button has already been pressed, pass the
2727  * cancellation on to the case context.
2728  */
2729  if (cancelRequested) {
2730  cancel();
2731  }
2732  }
2733 
2739  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
2740  this.caseActionFuture = caseActionFuture;
2741  /*
2742  * If the cancel button has already been pressed, cancel the Future
2743  * of the task.
2744  */
2745  if (cancelRequested) {
2746  cancel();
2747  }
2748  }
2749 
2755  @Override
2756  public synchronized void actionPerformed(ActionEvent event) {
2757  cancel();
2758  }
2759 
2763  private void cancel() {
2764  /*
2765  * At a minimum, set the cancellation requested flag of this
2766  * listener.
2767  */
2768  this.cancelRequested = true;
2769  if (null != this.caseContext) {
2770  /*
2771  * Set the cancellation request flag and display the
2772  * cancellation message in the progress indicator for the case
2773  * context associated with this listener.
2774  */
2776  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
2777  if (progressIndicator instanceof ModalDialogProgressIndicator) {
2778  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2779  }
2780  }
2781  this.caseContext.requestCancel();
2782  }
2783  if (null != this.caseActionFuture) {
2784  /*
2785  * Cancel the Future of the task associated with this listener.
2786  * Note that the task thread will be interrupted if the task is
2787  * blocked.
2788  */
2789  this.caseActionFuture.cancel(true);
2790  }
2791  }
2792  }
2793 
2797  private static class TaskThreadFactory implements ThreadFactory {
2798 
2799  private final String threadName;
2800 
2801  private TaskThreadFactory(String threadName) {
2802  this.threadName = threadName;
2803  }
2804 
2805  @Override
2806  public Thread newThread(Runnable task) {
2807  return new Thread(task, threadName);
2808  }
2809 
2810  }
2811 
2819  @Deprecated
2820  public static String getAppName() {
2821  return UserPreferences.getAppName();
2822  }
2823 
2843  @Deprecated
2844  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
2845  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
2846  }
2847 
2868  @Deprecated
2869  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
2870  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
2871  }
2872 
2884  @Deprecated
2885  public static void open(String caseMetadataFilePath) throws CaseActionException {
2886  openAsCurrentCase(caseMetadataFilePath);
2887  }
2888 
2898  @Deprecated
2899  public void closeCase() throws CaseActionException {
2900  closeCurrentCase();
2901  }
2902 
2908  @Deprecated
2909  public static void invokeStartupDialog() {
2911  }
2912 
2926  @Deprecated
2927  public static String convertTimeZone(String timeZoneId) {
2928  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
2929  }
2930 
2940  @Deprecated
2941  public static boolean pathExists(String filePath) {
2942  return new File(filePath).isFile();
2943  }
2944 
2953  @Deprecated
2954  public static String getAutopsyVersion() {
2955  return Version.getVersion();
2956  }
2957 
2965  @Deprecated
2966  public static boolean existsCurrentCase() {
2967  return isCaseOpen();
2968  }
2969 
2979  @Deprecated
2980  public static String getModulesOutputDirRelPath() {
2981  return "ModuleOutput"; //NON-NLS
2982  }
2983 
2993  @Deprecated
2994  public static PropertyChangeSupport
2996  return new PropertyChangeSupport(Case.class
2997  );
2998  }
2999 
3008  @Deprecated
3009  public String getModulesOutputDirAbsPath() {
3010  return getModuleDirectory();
3011  }
3012 
3027  @Deprecated
3028  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
3029  try {
3030  Image newDataSource = caseDb.getImageById(imgId);
3031  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
3032  return newDataSource;
3033  } catch (TskCoreException ex) {
3034  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
3035  }
3036  }
3037 
3045  @Deprecated
3046  public Set<TimeZone> getTimeZone() {
3047  return getTimeZones();
3048  }
3049 
3060  @Deprecated
3061  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
3062  deleteReports(reports);
3063  }
3064 
3065 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:143
List< Content > getDataSources()
Definition: Case.java:1495
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1623
Case(CaseMetadata caseMetaData)
Definition: Case.java:1831
static CaseType fromString(String typeName)
Definition: Case.java:186
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:140
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:554
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1675
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:3028
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
Definition: Case.java:1102
static boolean existsCurrentCase()
Definition: Case.java:2966
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:412
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:142
static final int RESOURCES_LOCK_TIMOUT_HOURS
Definition: Case.java:130
static final String EXPORT_FOLDER
Definition: Case.java:134
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:906
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1634
static volatile Frame mainFrame
Definition: Case.java:145
static String convertTimeZone(String timeZoneId)
Definition: Case.java:2927
static boolean driveExists(String path)
Definition: DriveUtils.java:66
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1649
static final String CACHE_FOLDER
Definition: Case.java:133
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1822
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1690
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1127
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1601
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:2869
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:131
byte[] getNodeData(CategoryNode category, String nodePath)
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:719
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:3061
volatile ExecutorService caseLockingExecutor
Definition: Case.java:148
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:2723
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1254
static boolean isValidName(String caseName)
Definition: Case.java:496
void acquireSharedCaseDirLock(String caseDir)
Definition: Case.java:2649
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2282
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2100
CollaborationMonitor collaborationMonitor
Definition: Case.java:151
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2440
void releaseSharedCaseDirLock(String caseDir)
Definition: Case.java:2665
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:2980
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2137
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:2756
static final String MODULE_FOLDER
Definition: Case.java:139
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2310
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:2844
synchronized void openRemoteEventChannel(String channelName)
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2161
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:866
void open(boolean isNewCase)
Definition: Case.java:1860
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:581
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:472
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
Definition: Case.java:129
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2539
void setNodeData(CategoryNode category, String nodePath, byte[] data)
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1664
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:2995
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:400
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:2739
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2236
void openAppServiceCaseResources(ProgressIndicator progressIndicator)
Definition: Case.java:2332
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:462
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2117
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2211
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1745
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1588
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1708
static boolean pathExists(String filePath)
Definition: Case.java:2941
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2042
static void open(String caseMetadataFilePath)
Definition: Case.java:2885
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:826
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2682
boolean equalsName(String otherTypeName)
Definition: Case.java:244
static final String EVENT_CHANNEL_NAME
Definition: Case.java:132
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:437
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:135
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:146
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1559
CoordinationService.Lock caseDirLock
Definition: Case.java:149
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:452
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1612
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:141
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:527
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1573
static final String CONFIG_FOLDER
Definition: Case.java:137
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:482
static final Object caseActionSerializationLock
Definition: Case.java:144
void open(boolean isNewCase, ProgressIndicator progressIndicator)
Definition: Case.java:1983
static final String REPORTS_FOLDER
Definition: Case.java:136
static final String TEMP_FOLDER
Definition: Case.java:138
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:427
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2187
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:1012

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.