Autopsy  4.16.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 2012-2020 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 com.google.common.annotations.Beta;
23 import com.google.common.eventbus.Subscribe;
24 import com.google.common.util.concurrent.ThreadFactoryBuilder;
26 import java.awt.Frame;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.beans.PropertyChangeListener;
30 import java.beans.PropertyChangeSupport;
31 import java.io.File;
32 import java.nio.file.InvalidPathException;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.sql.Connection;
36 import java.sql.DriverManager;
37 import java.sql.ResultSet;
38 import java.sql.SQLException;
39 import java.sql.Statement;
40 import java.text.SimpleDateFormat;
41 import java.util.Collection;
42 import java.util.Date;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.TimeZone;
49 import java.util.UUID;
50 import java.util.concurrent.CancellationException;
51 import java.util.concurrent.ExecutionException;
52 import java.util.concurrent.ExecutorService;
53 import java.util.concurrent.Executors;
54 import java.util.concurrent.Future;
55 import java.util.concurrent.ThreadFactory;
56 import java.util.concurrent.TimeUnit;
57 import java.util.logging.Level;
58 import java.util.stream.Collectors;
59 import java.util.stream.Stream;
60 import javax.annotation.concurrent.GuardedBy;
61 import javax.annotation.concurrent.ThreadSafe;
62 import javax.swing.JOptionPane;
63 import javax.swing.SwingUtilities;
64 import org.openide.util.Lookup;
65 import org.openide.util.NbBundle;
66 import org.openide.util.NbBundle.Messages;
67 import org.openide.util.actions.CallableSystemAction;
68 import org.openide.windows.WindowManager;
122 import org.sleuthkit.datamodel.Blackboard;
123 import org.sleuthkit.datamodel.BlackboardArtifact;
124 import org.sleuthkit.datamodel.BlackboardArtifactTag;
125 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
126 import org.sleuthkit.datamodel.Content;
127 import org.sleuthkit.datamodel.ContentTag;
128 import org.sleuthkit.datamodel.DataSource;
129 import org.sleuthkit.datamodel.FileSystem;
130 import org.sleuthkit.datamodel.Image;
131 import org.sleuthkit.datamodel.Report;
132 import org.sleuthkit.datamodel.SleuthkitCase;
133 import org.sleuthkit.datamodel.TimelineManager;
134 import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
135 import org.sleuthkit.datamodel.TskCoreException;
136 import org.sleuthkit.datamodel.TskDataException;
137 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
138 
142 public class Case {
143 
144  private static final String CASE_TEMP_DIR = Case.class.getSimpleName();
145  private static final int CASE_LOCK_TIMEOUT_MINS = 1;
146  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
147  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
148  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
149  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
150  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
151  private static final String LOG_FOLDER = "Log"; //NON-NLS
152  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
153  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
154  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
155  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
156  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
157  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
158  private static final Logger logger = Logger.getLogger(Case.class.getName());
160  private static final Object caseActionSerializationLock = new Object();
161  private static Future<?> backgroundOpenFileSystemsFuture = null;
162  private static final ExecutorService openFileSystemsExecutor
163  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
164  private static volatile Frame mainFrame;
165  private static volatile Case currentCase;
166  private final CaseMetadata metadata;
167  private volatile ExecutorService caseActionExecutor;
169  private SleuthkitCase caseDb;
171  private CollaborationMonitor collaborationMonitor;
173 
174  /*
175  * Get a reference to the main window of the desktop application to use to
176  * parent pop up dialogs and initialize the application name for use in
177  * changing the main window title.
178  */
179  static {
180  WindowManager.getDefault().invokeWhenUIReady(() -> {
181  mainFrame = WindowManager.getDefault().getMainWindow();
182  });
183  }
184 
188  public enum CaseType {
189 
190  SINGLE_USER_CASE("Single-user case"), //NON-NLS
191  MULTI_USER_CASE("Multi-user case"); //NON-NLS
192 
193  private final String typeName;
194 
202  public static CaseType fromString(String typeName) {
203  if (typeName != null) {
204  for (CaseType c : CaseType.values()) {
205  if (typeName.equalsIgnoreCase(c.toString())) {
206  return c;
207  }
208  }
209  }
210  return null;
211  }
212 
218  @Override
219  public String toString() {
220  return typeName;
221  }
222 
228  @Messages({
229  "Case_caseType_singleUser=Single-user case",
230  "Case_caseType_multiUser=Multi-user case"
231  })
233  if (fromString(typeName) == SINGLE_USER_CASE) {
234  return Bundle.Case_caseType_singleUser();
235  } else {
236  return Bundle.Case_caseType_multiUser();
237  }
238  }
239 
245  private CaseType(String typeName) {
246  this.typeName = typeName;
247  }
248 
259  @Deprecated
260  public boolean equalsName(String otherTypeName) {
261  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
262  }
263 
264  };
265 
270  public enum Events {
271 
279  @Deprecated
288  @Deprecated
297  @Deprecated
408  /*
409  * An item in the central repository has had its comment modified. The
410  * old value is null, the new value is string for current comment.
411  */
413 
414  };
415 
421  private final class SleuthkitEventListener {
422 
423  @Subscribe
424  public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
425  eventPublisher.publish(new TimelineEventAddedEvent(event));
426  }
427 
428  @SuppressWarnings("deprecation")
429  @Subscribe
430  public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) {
431  for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) {
432  /*
433  * IngestServices.fireModuleDataEvent is deprecated to
434  * discourage ingest module writers from using it (they should
435  * use org.sleuthkit.datamodel.Blackboard.postArtifact(s)
436  * instead), but a way to publish
437  * Blackboard.ArtifactsPostedEvents from the SleuthKit layer as
438  * Autopsy ModuleDataEvents is still needed.
439  */
441  event.getModuleName(),
442  artifactType,
443  event.getArtifacts(artifactType)));
444  }
445  }
446  }
447 
454  public static void addPropertyChangeListener(PropertyChangeListener listener) {
455  addEventSubscriber(Stream.of(Events.values())
456  .map(Events::toString)
457  .collect(Collectors.toSet()), listener);
458  }
459 
466  public static void removePropertyChangeListener(PropertyChangeListener listener) {
467  removeEventSubscriber(Stream.of(Events.values())
468  .map(Events::toString)
469  .collect(Collectors.toSet()), listener);
470  }
471 
480  @Deprecated
481  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
482  eventPublisher.addSubscriber(eventNames, subscriber);
483  }
484 
491  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
492  eventTypes.forEach((Events event) -> {
493  eventPublisher.addSubscriber(event.toString(), subscriber);
494  });
495  }
496 
505  @Deprecated
506  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
507  eventPublisher.addSubscriber(eventName, subscriber);
508  }
509 
516  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
517  eventPublisher.removeSubscriber(eventName, subscriber);
518  }
519 
526  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
527  eventPublisher.removeSubscriber(eventNames, subscriber);
528  }
529 
536  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
537  eventTypes.forEach((Events event) -> {
538  eventPublisher.removeSubscriber(event.toString(), subscriber);
539  });
540  }
541 
550  public static boolean isValidName(String caseName) {
551  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
552  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
553  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
554  }
555 
580  @Deprecated
581  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
582  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
583  }
584 
604  @Messages({
605  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
606  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
607  })
608  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
609  if (caseDetails.getCaseDisplayName().isEmpty()) {
610  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
611  }
612  if (caseDir.isEmpty()) {
613  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
614  }
615  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
616  }
617 
631  @Messages({
632  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
633  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
634  })
635  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
637  try {
638  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
639  } catch (CaseMetadataException ex) {
640  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
641  }
643  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
644  }
645  openAsCurrentCase(new Case(metadata), false);
646  }
647 
653  public static boolean isCaseOpen() {
654  return currentCase != null;
655  }
656 
664  public static Case getCurrentCase() {
665  try {
666  return getCurrentCaseThrows();
667  } catch (NoCurrentCaseException ex) {
668  /*
669  * Throw a runtime exception, since this is a programming error.
670  */
671  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
672  }
673  }
674 
690  /*
691  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
692  * case method.
693  */
694  Case openCase = currentCase;
695  if (openCase == null) {
696  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
697  } else {
698  return openCase;
699  }
700  }
701 
710  @Messages({
711  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
712  "Case.progressIndicatorTitle.closingCase=Closing Case"
713  })
714  public static void closeCurrentCase() throws CaseActionException {
715  synchronized (caseActionSerializationLock) {
716  if (null == currentCase) {
717  return;
718  }
719  Case closedCase = currentCase;
720  try {
721  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
722  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
723  currentCase = null;
724  closedCase.doCloseCaseAction();
725  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
726  } catch (CaseActionException ex) {
727  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
728  throw ex;
729  } finally {
732  }
733  }
734  }
735  }
736 
745  public static void deleteCurrentCase() throws CaseActionException {
746  synchronized (caseActionSerializationLock) {
747  if (null == currentCase) {
748  return;
749  }
750  CaseMetadata metadata = currentCase.getMetadata();
752  deleteCase(metadata);
753  }
754  }
755 
766  @Messages({
767  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
768  })
769  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
770  synchronized (caseActionSerializationLock) {
771  if (null == currentCase) {
772  return;
773  }
774 
775  /*
776  * Close the current case to release the shared case lock.
777  */
778  CaseMetadata caseMetadata = currentCase.getMetadata();
780 
781  /*
782  * Re-open the case with an exclusive case lock, delete the data
783  * source, and close the case again, releasing the exclusive case
784  * lock.
785  */
786  Case theCase = new Case(caseMetadata);
787  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
788 
789  /*
790  * Re-open the case with a shared case lock.
791  */
792  openAsCurrentCase(new Case(caseMetadata), false);
793  }
794  }
795 
807  @Messages({
808  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
809  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
810  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
811  })
812  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
813  synchronized (caseActionSerializationLock) {
814  if (null != currentCase) {
815  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
816  }
817  }
818 
819  ProgressIndicator progressIndicator;
821  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
822  } else {
823  progressIndicator = new LoggingProgressIndicator();
824  }
825  progressIndicator.start(Bundle.Case_progressMessage_preparing());
826  try {
827  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
828  deleteSingleUserCase(metadata, progressIndicator);
829  } else {
830  try {
831  deleteMultiUserCase(metadata, progressIndicator);
832  } catch (InterruptedException ex) {
833  /*
834  * Note that task cancellation is not currently supported
835  * for this code path, so this catch block is not expected
836  * to be executed.
837  */
838  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
839  }
840  }
841  } finally {
842  progressIndicator.finish();
843  }
844  }
845 
856  @Messages({
857  "Case.progressIndicatorTitle.creatingCase=Creating Case",
858  "Case.progressIndicatorTitle.openingCase=Opening Case",
859  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
860  })
861  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
862  synchronized (caseActionSerializationLock) {
863  if (null != currentCase) {
864  try {
866  } catch (CaseActionException ex) {
867  /*
868  * Notify the user and continue (the error has already been
869  * logged in closeCurrentCase.
870  */
871  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
872  }
873  }
874  try {
875  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
876  String progressIndicatorTitle;
878  if (isNewCase) {
879  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
880  openCaseAction = newCurrentCase::create;
881  } else {
882  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
883  openCaseAction = newCurrentCase::open;
884  }
885  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
886  currentCase = newCurrentCase;
887  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
889  updateGUIForCaseOpened(newCurrentCase);
890  }
891  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
892  } catch (CaseActionCancelledException ex) {
893  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
894  throw ex;
895  } catch (CaseActionException ex) {
896  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
897  throw ex;
898  }
899  }
900  }
901 
910  private static String displayNameToUniqueName(String caseDisplayName) {
911  /*
912  * Replace all non-ASCII characters.
913  */
914  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
915 
916  /*
917  * Replace all control characters.
918  */
919  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
920 
921  /*
922  * Replace /, \, :, ?, space, ' ".
923  */
924  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
925 
926  /*
927  * Make it all lowercase.
928  */
929  uniqueCaseName = uniqueCaseName.toLowerCase();
930 
931  /*
932  * Add a time stamp for uniqueness.
933  */
934  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
935  Date date = new Date();
936  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
937 
938  return uniqueCaseName;
939  }
940 
950  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
951  /*
952  * Check the case directory path and permissions. The case directory may
953  * already exist.
954  */
955  File caseDir = new File(caseDirPath);
956  if (caseDir.exists()) {
957  if (caseDir.isFile()) {
958  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
959  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
960  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
961  }
962  }
963 
964  /*
965  * Create the case directory, if it does not already exist.
966  */
967  if (!caseDir.mkdirs()) {
968  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
969  }
970 
971  /*
972  * Create the subdirectories of the case directory, if they do not
973  * already exist. Note that multi-user cases get an extra layer of
974  * subdirectories, one subdirectory per application host machine.
975  */
976  String hostPathComponent = "";
977  if (caseType == CaseType.MULTI_USER_CASE) {
978  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
979  }
980 
981  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
982  if (!exportDir.toFile().mkdirs()) {
983  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
984  }
985 
986  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
987  if (!logsDir.toFile().mkdirs()) {
988  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
989  }
990 
991  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
992  if (!cacheDir.toFile().mkdirs()) {
993  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
994  }
995 
996  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
997  if (!moduleOutputDir.toFile().mkdirs()) {
998  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
999  }
1000 
1001  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1002  if (!reportsDir.toFile().mkdirs()) {
1003  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1004  }
1005  }
1006 
1014  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1015  Map<Long, String> imgPaths = new HashMap<>();
1016  try {
1017  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1018  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1019  if (entry.getValue().size() > 0) {
1020  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1021  }
1022  }
1023  } catch (TskCoreException ex) {
1024  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1025  }
1026  return imgPaths;
1027  }
1028 
1039  @Messages({
1040  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1041  })
1042  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1043  try {
1044  Path caseDirPath = Paths.get(caseDir);
1045  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1046  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1047  return lock;
1048  } catch (InterruptedException ex) {
1049  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1050  } catch (CoordinationServiceException ex) {
1051  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1052  }
1053  }
1054 
1055  private static String getNameForTitle() {
1056  //Method should become unnecessary once technical debt story 3334 is done.
1057  if (UserPreferences.getAppName().equals(Version.getName())) {
1058  //Available version number is version number for this application
1059  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1060  } else {
1061  return UserPreferences.getAppName();
1062  }
1063  }
1064 
1068  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1070  SwingUtilities.invokeLater(() -> {
1071  /*
1072  * If the case database was upgraded for a new schema and a
1073  * backup database was created, notify the user.
1074  */
1075  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1076  String backupDbPath = caseDb.getBackupDatabasePath();
1077  if (null != backupDbPath) {
1078  JOptionPane.showMessageDialog(
1079  mainFrame,
1080  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1081  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1082  JOptionPane.INFORMATION_MESSAGE);
1083  }
1084 
1085  /*
1086  * Look for the files for the data sources listed in the case
1087  * database and give the user the opportunity to locate any that
1088  * are missing.
1089  */
1090  Map<Long, String> imgPaths = getImagePaths(caseDb);
1091  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1092  long obj_id = entry.getKey();
1093  String path = entry.getValue();
1094  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1095  if (!fileExists) {
1096  int response = JOptionPane.showConfirmDialog(
1097  mainFrame,
1098  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1099  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1100  JOptionPane.YES_NO_OPTION);
1101  if (response == JOptionPane.YES_OPTION) {
1102  MissingImageDialog.makeDialog(obj_id, caseDb);
1103  } else {
1104  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1105 
1106  }
1107  }
1108  }
1109 
1110  /*
1111  * Enable the case-specific actions.
1112  */
1113  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1114  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1115  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1116  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1117  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1118  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1119  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1120  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1121  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1122  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1123 
1124  /*
1125  * Add the case to the recent cases tracker that supplies a list
1126  * of recent cases to the recent cases menu item and the
1127  * open/create case dialog.
1128  */
1129  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1130 
1131  /*
1132  * Open the top components (windows within the main application
1133  * window).
1134  *
1135  * Note: If the core windows are not opened here, they will be
1136  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1137  * method on a DATA_SOURCE_ADDED event.
1138  */
1139  if (newCurrentCase.hasData()) {
1141  }
1142 
1143  /*
1144  * Reset the main window title to:
1145  *
1146  * [curent case display name] - [application name].
1147  */
1148  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1149  });
1150  }
1151  }
1152 
1153  /*
1154  * Update the GUI to to reflect the lack of a current case.
1155  */
1156  private static void updateGUIForCaseClosed() {
1158  SwingUtilities.invokeLater(() -> {
1159  /*
1160  * Close the top components (windows within the main application
1161  * window).
1162  */
1164 
1165  /*
1166  * Disable the case-specific menu items.
1167  */
1168  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1169  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1170  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1171  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1172  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1173  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1174  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1175  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1176  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1177  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1178 
1179  /*
1180  * Clear the notifications in the notfier component in the lower
1181  * right hand corner of the main application window.
1182  */
1184 
1185  /*
1186  * Reset the main window title to be just the application name,
1187  * instead of [curent case display name] - [application name].
1188  */
1189  mainFrame.setTitle(getNameForTitle());
1190  });
1191  }
1192  }
1193 
1199  public SleuthkitCase getSleuthkitCase() {
1200  return this.caseDb;
1201  }
1202 
1209  return caseServices;
1210  }
1211 
1218  return metadata.getCaseType();
1219  }
1220 
1226  public String getCreatedDate() {
1227  return metadata.getCreatedDate();
1228  }
1229 
1235  public String getName() {
1236  return metadata.getCaseName();
1237  }
1238 
1244  public String getDisplayName() {
1245  return metadata.getCaseDisplayName();
1246  }
1247 
1253  public String getNumber() {
1254  return metadata.getCaseNumber();
1255  }
1256 
1262  public String getExaminer() {
1263  return metadata.getExaminer();
1264  }
1265 
1271  public String getExaminerPhone() {
1272  return metadata.getExaminerPhone();
1273  }
1274 
1280  public String getExaminerEmail() {
1281  return metadata.getExaminerEmail();
1282  }
1283 
1289  public String getCaseNotes() {
1290  return metadata.getCaseNotes();
1291  }
1292 
1298  public String getCaseDirectory() {
1299  return metadata.getCaseDirectory();
1300  }
1301 
1310  public String getOutputDirectory() {
1311  String caseDirectory = getCaseDirectory();
1312  Path hostPath;
1313  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1314  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1315  } else {
1316  hostPath = Paths.get(caseDirectory);
1317  }
1318  if (!hostPath.toFile().exists()) {
1319  hostPath.toFile().mkdirs();
1320  }
1321  return hostPath.toString();
1322  }
1323 
1330  public String getTempDirectory() {
1331  // get temp folder scoped to the combination of case name and timestamp
1332  // provided by getName()
1333  Path path = Paths.get(UserPreferences.getAppTempDirectory(), CASE_TEMP_DIR, getName());
1334  File f = path.toFile();
1335  // verify that the folder exists
1336  if (!f.exists()) {
1337  f.mkdirs();
1338  }
1339 
1340  return path.toAbsolutePath().toString();
1341  }
1342 
1349  public String getCacheDirectory() {
1350  return getOrCreateSubdirectory(CACHE_FOLDER);
1351  }
1352 
1359  public String getExportDirectory() {
1360  return getOrCreateSubdirectory(EXPORT_FOLDER);
1361  }
1362 
1369  public String getLogDirectoryPath() {
1370  return getOrCreateSubdirectory(LOG_FOLDER);
1371  }
1372 
1379  public String getReportDirectory() {
1380  return getOrCreateSubdirectory(REPORTS_FOLDER);
1381  }
1382 
1389  public String getConfigDirectory() {
1390  return getOrCreateSubdirectory(CONFIG_FOLDER);
1391  }
1392 
1399  public String getModuleDirectory() {
1400  return getOrCreateSubdirectory(MODULE_FOLDER);
1401  }
1402 
1411  Path path = Paths.get(getModuleDirectory());
1413  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1414  } else {
1415  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1416  }
1417  }
1418 
1428  public List<Content> getDataSources() throws TskCoreException {
1429  return caseDb.getRootObjects();
1430  }
1431 
1437  public Set<TimeZone> getTimeZones() {
1438  Set<TimeZone> timezones = new HashSet<>();
1439  try {
1440  for (Content c : getDataSources()) {
1441  final Content dataSource = c.getDataSource();
1442  if ((dataSource != null) && (dataSource instanceof Image)) {
1443  Image image = (Image) dataSource;
1444  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1445  }
1446  }
1447  } catch (TskCoreException ex) {
1448  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1449  }
1450  return timezones;
1451  }
1452 
1459  public String getTextIndexName() {
1460  return getMetadata().getTextIndexName();
1461  }
1462 
1469  public boolean hasData() {
1470  boolean hasDataSources = false;
1471  try {
1472  hasDataSources = (getDataSources().size() > 0);
1473  } catch (TskCoreException ex) {
1474  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1475  }
1476  return hasDataSources;
1477  }
1478 
1489  public void notifyAddingDataSource(UUID eventId) {
1490  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1491  }
1492 
1503  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1504  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1505  }
1506 
1518  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1519  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1520  }
1521 
1531  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1532  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1533  }
1534 
1542  public void notifyContentTagAdded(ContentTag newTag) {
1543  notifyContentTagAdded(newTag, null);
1544  }
1545 
1555  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1556  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1557  }
1558 
1566  public void notifyContentTagDeleted(ContentTag deletedTag) {
1567  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1568  }
1569 
1577  public void notifyTagDefinitionChanged(String changedTagName) {
1578  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1579  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1580  }
1581 
1592  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1593  try {
1594  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1595  } catch (NoCurrentCaseException ex) {
1596  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1597  }
1598  }
1599 
1607  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1608  notifyBlackBoardArtifactTagAdded(newTag, null);
1609  }
1610 
1620  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1621  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1622  }
1623 
1631  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1632  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1633  }
1634 
1646  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1647  addReport(localPath, srcModuleName, reportName, null);
1648  }
1649 
1664  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1665  String normalizedLocalPath;
1666  try {
1667  if (localPath.toLowerCase().contains("http:")) {
1668  normalizedLocalPath = localPath;
1669  } else {
1670  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1671  }
1672  } catch (InvalidPathException ex) {
1673  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1674  throw new TskCoreException(errorMsg, ex);
1675  }
1676  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1677  eventPublisher.publish(new ReportAddedEvent(report));
1678  return report;
1679  }
1680 
1689  public List<Report> getAllReports() throws TskCoreException {
1690  return this.caseDb.getAllReports();
1691  }
1692 
1701  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1702  for (Report report : reports) {
1703  this.caseDb.deleteReport(report);
1704  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1705  }
1706  }
1707 
1713  CaseMetadata getMetadata() {
1714  return metadata;
1715  }
1716 
1724  @Messages({
1725  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1726  })
1727  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1728  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1729  try {
1730  metadata.setCaseDetails(caseDetails);
1731  } catch (CaseMetadataException ex) {
1732  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1733  }
1734  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1735  try {
1736  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
1737  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
1738  CaseNodeData.writeCaseNodeData(nodeData);
1739  } catch (CaseNodeDataException | InterruptedException ex) {
1740  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
1741  }
1742  }
1743  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1744  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1745  }
1746  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1747  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1748  }
1749  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1750  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1751  }
1752  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1753  if (RuntimeProperties.runningWithGUI()) {
1754  SwingUtilities.invokeLater(() -> {
1755  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1756  try {
1757  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1758  } catch (Exception ex) {
1759  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1760  }
1761  });
1762  }
1763  }
1764 
1777  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1778  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
1779  }
1780 
1786  private Case(CaseMetadata caseMetaData) {
1787  metadata = caseMetaData;
1788  sleuthkitEventListener = new SleuthkitEventListener();
1789  }
1790 
1820  @Messages({
1821  "Case.progressIndicatorCancelButton.label=Cancel",
1822  "Case.progressMessage.preparing=Preparing...",
1823  "Case.progressMessage.cancelling=Cancelling...",
1824  "Case.exceptionMessage.cancelled=Cancelled.",
1825  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1826  })
1827  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
1828  /*
1829  * Create and start either a GUI progress indicator (with or without a
1830  * cancel button) or a logging progress indicator.
1831  */
1832  CancelButtonListener cancelButtonListener = null;
1833  ProgressIndicator progressIndicator;
1835  if (allowCancellation) {
1836  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1837  progressIndicator = new ModalDialogProgressIndicator(
1838  mainFrame,
1839  progressIndicatorTitle,
1840  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1841  Bundle.Case_progressIndicatorCancelButton_label(),
1842  cancelButtonListener);
1843  } else {
1844  progressIndicator = new ModalDialogProgressIndicator(
1845  mainFrame,
1846  progressIndicatorTitle);
1847  }
1848  } else {
1849  progressIndicator = new LoggingProgressIndicator();
1850  }
1851  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1852 
1853  /*
1854  * Do the case action in the single thread in the case action executor.
1855  * If the case is a multi-user case, a case lock is acquired and held
1856  * until explictly released and an exclusive case resources lock is
1857  * aquired and held for the duration of the action.
1858  */
1859  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1860  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
1861  Future<Void> future = caseActionExecutor.submit(() -> {
1862  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1863  caseAction.execute(progressIndicator, additionalParams);
1864  } else {
1865  acquireCaseLock(caseLockType);
1866  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
1867  if (null == resourcesLock) {
1868  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
1869  }
1870  caseAction.execute(progressIndicator, additionalParams);
1871  } catch (CaseActionException ex) {
1872  releaseCaseLock();
1873  throw ex;
1874  }
1875  }
1876  return null;
1877  });
1878  if (null != cancelButtonListener) {
1879  cancelButtonListener.setCaseActionFuture(future);
1880  }
1881 
1882  /*
1883  * Wait for the case action task to finish.
1884  */
1885  try {
1886  future.get();
1887  } catch (InterruptedException discarded) {
1888  /*
1889  * The thread this method is running in has been interrupted.
1890  */
1891  if (null != cancelButtonListener) {
1892  cancelButtonListener.actionPerformed(null);
1893  } else {
1894  future.cancel(true);
1895  }
1896  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1897  } catch (CancellationException discarded) {
1898  /*
1899  * The case action has been cancelled.
1900  */
1901  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1902  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1903  } catch (ExecutionException ex) {
1904  /*
1905  * The case action has thrown an exception.
1906  */
1907  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1908  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1909  } finally {
1910  progressIndicator.finish();
1911  }
1912  }
1913 
1929  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
1930  assert (additionalParams == null);
1931  try {
1933  createCaseDirectoryIfDoesNotExist(progressIndicator);
1935  switchLoggingToCaseLogsDirectory(progressIndicator);
1937  saveCaseMetadataToFile(progressIndicator);
1939  createCaseNodeData(progressIndicator);
1942  createCaseDatabase(progressIndicator);
1944  openCaseLevelServices(progressIndicator);
1946  openAppServiceCaseResources(progressIndicator, true);
1948  openCommunicationChannels(progressIndicator);
1949  return null;
1950 
1951  } catch (CaseActionException ex) {
1952  /*
1953  * Cancellation or failure. The sleep is a little hack to clear the
1954  * interrupted flag for this thread if this is a cancellation
1955  * scenario, so that the clean up can run to completion in the
1956  * current thread.
1957  */
1958  try {
1959  Thread.sleep(1);
1960  } catch (InterruptedException discarded) {
1961  }
1962  close(progressIndicator);
1963  throw ex;
1964  }
1965  }
1966 
1981  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
1982  assert (additionalParams == null);
1983  try {
1985  switchLoggingToCaseLogsDirectory(progressIndicator);
1987  updateCaseNodeData(progressIndicator);
1989  deleteTempfilesFromCaseDirectory(progressIndicator);
1991  openCaseDataBase(progressIndicator);
1993  openCaseLevelServices(progressIndicator);
1995  openAppServiceCaseResources(progressIndicator, false);
1997  openCommunicationChannels(progressIndicator);
2000  return null;
2001 
2002  } catch (CaseActionException ex) {
2003  /*
2004  * Cancellation or failure. The sleep is a little hack to clear the
2005  * interrupted flag for this thread if this is a cancellation
2006  * scenario, so that the clean up can run to completion in the
2007  * current thread.
2008  */
2009  try {
2010  Thread.sleep(1);
2011  } catch (InterruptedException discarded) {
2012  }
2013  close(progressIndicator);
2014  throw ex;
2015  }
2016  }
2017 
2027  @Messages({
2028  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2029  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2030  })
2032  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2033  backgroundOpenFileSystemsFuture.cancel(true);
2034  }
2035 
2037  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2038  }
2039 
2044  private static class BackgroundOpenFileSystemsTask implements Runnable {
2045 
2046  private final SleuthkitCase tskCase;
2047  private final String caseName;
2048  private final long MAX_IMAGE_THRESHOLD = 100;
2050 
2059  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2060  this.tskCase = tskCase;
2061  this.progressIndicator = progressIndicator;
2062  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2063  }
2064 
2072  private void checkIfCancelled() throws InterruptedException {
2073  if (Thread.interrupted()) {
2074  throw new InterruptedException();
2075  }
2076  }
2077 
2083  private List<Image> getImages() {
2084  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2085  try {
2086  return this.tskCase.getImages();
2087  } catch (TskCoreException ex) {
2088  logger.log(
2089  Level.SEVERE,
2090  String.format("Could not obtain images while opening case: %s.", caseName),
2091  ex);
2092 
2093  return null;
2094  }
2095  }
2096 
2106  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2107  byte[] tempBuff = new byte[512];
2108 
2109  for (Image image : images) {
2110  String imageStr = image.getName();
2111 
2112  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2113 
2114  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2115  checkIfCancelled();
2116  for (FileSystem fileSystem : fileSystems) {
2117  fileSystem.read(tempBuff, 0, 512);
2118  checkIfCancelled();
2119  }
2120 
2121  }
2122  }
2123 
2124  @Override
2125  public void run() {
2126  try {
2127  checkIfCancelled();
2128  List<Image> images = getImages();
2129  if (images == null) {
2130  return;
2131  }
2132 
2133  if (images.size() > MAX_IMAGE_THRESHOLD) {
2134  // If we have a large number of images, don't try to preload anything
2135  logger.log(
2136  Level.INFO,
2137  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2138  return;
2139  }
2140 
2141  checkIfCancelled();
2142  openFileSystems(images);
2143  } catch (InterruptedException ex) {
2144  logger.log(
2145  Level.INFO,
2146  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2147  } catch (Exception ex) {
2148  // Exception firewall
2149  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2150  }
2151  }
2152 
2153  }
2154 
2169  @Messages({
2170  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2171  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2172  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2173  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2174  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2175  assert (additionalParams instanceof Long);
2176  open(progressIndicator, null);
2177  try {
2178  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2179  Long dataSourceObjectID = (Long) additionalParams;
2180  try {
2181  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2182  if (dataSource == null) {
2183  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2184  }
2185  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2186  } catch (TskDataException | TskCoreException ex) {
2187  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2188  }
2189  try {
2190  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2191  } catch (KeywordSearchServiceException ex) {
2192  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2193  }
2194  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2195  return null;
2196  } finally {
2197  close(progressIndicator);
2198  releaseCaseLock();
2199  }
2200  }
2201 
2212  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2213 
2214  if (portableCaseFolder.exists()) {
2215  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2216  }
2217  if (!portableCaseFolder.mkdirs()) {
2218  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2219  }
2220 
2221  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2223  try {
2224  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2225  caseName, details, metadata);
2226  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2227  } catch (CaseMetadataException ex) {
2228  throw new TskCoreException("Error creating case metadata", ex);
2229  }
2230 
2231  // Create the Sleuthkit case
2232  SleuthkitCase portableSleuthkitCase;
2233  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2234  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2235 
2236  return portableSleuthkitCase;
2237  }
2238 
2249  if (Thread.currentThread().isInterrupted()) {
2250  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2251  }
2252  }
2253 
2264  @Messages({
2265  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2266  })
2267  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2268  /*
2269  * TODO (JIRA-2180): Always create the case directory as part of the
2270  * case creation process.
2271  */
2272  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2273  if (new File(metadata.getCaseDirectory()).exists() == false) {
2274  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2275  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2276  }
2277  }
2278 
2285  @Messages({
2286  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2287  })
2288  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2289  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2291  }
2292 
2304  @Messages({
2305  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2306  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2307  })
2308  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2309  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2310  try {
2311  this.metadata.writeToFile();
2312  } catch (CaseMetadataException ex) {
2313  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2314  }
2315  }
2316 
2328  @Messages({
2329  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2330  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2331  })
2332  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2334  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2335  try {
2336  CaseNodeData.createCaseNodeData(metadata);
2337  } catch (CaseNodeDataException | InterruptedException ex) {
2338  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2339  }
2340  }
2341  }
2342 
2354  @Messages({
2355  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2356  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2357  })
2358  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2360  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2361  try {
2363  nodeData.setLastAccessDate(new Date());
2364  CaseNodeData.writeCaseNodeData(nodeData);
2365  } catch (CaseNodeDataException | InterruptedException ex) {
2366  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2367  }
2368  }
2369  }
2370 
2376  @Messages({
2377  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2378  })
2379  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2380  /*
2381  * Clear the temp subdirectory of the case directory.
2382  */
2383  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2384  FileUtil.deleteDir(new File(this.getTempDirectory()));
2385  }
2386 
2398  @Messages({
2399  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2400  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2401  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2402  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2403  })
2404  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2405  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2406  try {
2407  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2408  /*
2409  * For single-user cases, the case database is a SQLite database
2410  * with a standard name, physically located in the case
2411  * directory.
2412  */
2413  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2414  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2415  } else {
2416  /*
2417  * For multi-user cases, the case database is a PostgreSQL
2418  * database with a name derived from the case display name,
2419  * physically located on the PostgreSQL database server.
2420  */
2421  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2422  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2423  }
2424  } catch (TskCoreException ex) {
2425  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2426  } catch (UserPreferencesException ex) {
2427  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2428  } catch (CaseMetadataException ex) {
2429  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2430  }
2431  }
2432 
2444  @Messages({
2445  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2446  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2447  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2448  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2449  })
2450  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2451  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2452  try {
2453  String databaseName = metadata.getCaseDatabaseName();
2454  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2455  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
2457  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2458  } else {
2459  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2460  }
2461  } catch (TskUnsupportedSchemaVersionException ex) {
2462  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2463  } catch (UserPreferencesException ex) {
2464  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2465  } catch (TskCoreException ex) {
2466  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2467  }
2468  }
2469 
2476  @Messages({
2477  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2478  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2479  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2480  this.caseServices = new Services(caseDb);
2481  /*
2482  * RC Note: JM put this initialization here. I'm not sure why. However,
2483  * my attempt to put it in the openCaseDatabase method seems to lead to
2484  * intermittent unchecked exceptions concerning a missing subscriber.
2485  */
2486  caseDb.registerForEvents(sleuthkitEventListener);
2487  }
2488 
2501  @NbBundle.Messages({
2502  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2503  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2504  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2505  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2506  })
2507  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2508  /*
2509  * Each service gets its own independently cancellable/interruptible
2510  * task, running in a named thread managed by an executor service, with
2511  * its own progress indicator. This allows for cancellation of the
2512  * opening of case resources for individual services. It also makes it
2513  * possible to ensure that each service task completes before the next
2514  * one starts by awaiting termination of the executor service.
2515  */
2516  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2517 
2518  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2519  )) {
2520  /*
2521  * Create a progress indicator for the task and start the task. If
2522  * running with a GUI, the progress indicator will be a dialog box
2523  * with a Cancel button.
2524  */
2525  CancelButtonListener cancelButtonListener = null;
2526  ProgressIndicator appServiceProgressIndicator;
2528  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2529  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2530  mainFrame,
2531  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2532  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2533  Bundle.Case_progressIndicatorCancelButton_label(),
2534  cancelButtonListener);
2535  } else {
2536  appServiceProgressIndicator = new LoggingProgressIndicator();
2537  }
2538  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2539  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2540  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2541  threadNameSuffix = threadNameSuffix.toLowerCase();
2542  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2543  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2544  Future<Void> future = executor.submit(() -> {
2545  service.openCaseResources(context);
2546  return null;
2547  });
2548  if (null != cancelButtonListener) {
2549  cancelButtonListener.setCaseContext(context);
2550  cancelButtonListener.setCaseActionFuture(future);
2551  }
2552 
2553  /*
2554  * Wait for the task to either be completed or
2555  * cancelled/interrupted, or for the opening of the case to be
2556  * cancelled.
2557  */
2558  try {
2559  future.get();
2560  } catch (InterruptedException discarded) {
2561  /*
2562  * The parent create/open case task has been cancelled.
2563  */
2564  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()));
2565  future.cancel(true);
2566  } catch (CancellationException discarded) {
2567  /*
2568  * The opening of case resources by the application service has
2569  * been cancelled, so the executor service has thrown. Note that
2570  * there is no guarantee the task itself has responded to the
2571  * cancellation request yet.
2572  */
2573  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()));
2574  } catch (ExecutionException ex) {
2575  /*
2576  * An exception was thrown while executing the task. The
2577  * case-specific application service resources are not
2578  * essential. Log an error and notify the user if running the
2579  * desktop GUI, but do not throw.
2580  */
2581  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2583  SwingUtilities.invokeLater(() -> {
2584  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2585  });
2586  }
2587  } finally {
2588  /*
2589  * Shut down the executor service and wait for it to finish.
2590  * This ensures that the task has finished. Without this, it
2591  * would be possible to start the next task before the current
2592  * task responded to a cancellation request.
2593  */
2595  appServiceProgressIndicator.finish();
2596  }
2598  }
2599  }
2600 
2612  @Messages({
2613  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2614  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2615  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2616  })
2617  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2618  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2619  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2620  try {
2621  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2623  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2624  } catch (AutopsyEventException ex) {
2625  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2626  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2627  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2628  }
2629  }
2630  }
2631 
2646  private void doCloseCaseAction() throws CaseActionException {
2647  /*
2648  * Set up either a GUI progress indicator without a Cancel button or a
2649  * logging progress indicator.
2650  */
2651  ProgressIndicator progressIndicator;
2653  progressIndicator = new ModalDialogProgressIndicator(
2654  mainFrame,
2655  Bundle.Case_progressIndicatorTitle_closingCase());
2656  } else {
2657  progressIndicator = new LoggingProgressIndicator();
2658  }
2659  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2660 
2661  /*
2662  * Closing a case is always done in the same non-UI thread that
2663  * opened/created the case. If the case is a multi-user case, this
2664  * ensures that case lock that is held as long as the case is open is
2665  * released in the same thread in which it was acquired, as is required
2666  * by the coordination service.
2667  */
2668  Future<Void> future = caseActionExecutor.submit(() -> {
2669  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2670  close(progressIndicator);
2671  } else {
2672  /*
2673  * Acquire an exclusive case resources lock to ensure only one
2674  * node at a time can create/open/upgrade/close the case
2675  * resources.
2676  */
2677  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2678  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2679  if (null == resourcesLock) {
2680  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2681  }
2682  close(progressIndicator);
2683  } finally {
2684  /*
2685  * Always release the case directory lock that was acquired
2686  * when the case was opened.
2687  */
2688  releaseCaseLock();
2689  }
2690  }
2691  return null;
2692  });
2693 
2694  try {
2695  future.get();
2696  } catch (InterruptedException | CancellationException unused) {
2697  /*
2698  * The wait has been interrupted by interrupting the thread running
2699  * this method. Not allowing cancellation of case closing, so ignore
2700  * the interrupt. Likewise, cancellation of the case closing task is
2701  * not supported.
2702  */
2703  } catch (ExecutionException ex) {
2704  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2705  } finally {
2706  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2707  progressIndicator.finish();
2708  }
2709  }
2710 
2716  @Messages({
2717  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2718  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2719  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2720  })
2721  private void close(ProgressIndicator progressIndicator) {
2723 
2724  /*
2725  * Stop sending/receiving case events to and from other nodes if this is
2726  * a multi-user case.
2727  */
2728  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2729  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2730  if (null != collaborationMonitor) {
2731  collaborationMonitor.shutdown();
2732  }
2733  eventPublisher.closeRemoteEventChannel();
2734  }
2735 
2736  /*
2737  * Allow all registered application services providers to close
2738  * resources related to the case.
2739  */
2740  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2742 
2743  /*
2744  * Close the case database.
2745  */
2746  if (null != caseDb) {
2747  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2748  caseDb.unregisterForEvents(sleuthkitEventListener);
2749  caseDb.close();
2750  }
2751 
2755  deleteTempfilesFromCaseDirectory(progressIndicator);
2756 
2757  /*
2758  * Switch the log directory.
2759  */
2760  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2762  }
2763 
2768  @Messages({
2769  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2770  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2771  })
2773  /*
2774  * Each service gets its own independently cancellable task, and thus
2775  * its own task progress indicator.
2776  */
2777  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2778  )) {
2779  ProgressIndicator progressIndicator;
2781  progressIndicator = new ModalDialogProgressIndicator(
2782  mainFrame,
2783  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2784  } else {
2785  progressIndicator = new LoggingProgressIndicator();
2786  }
2787  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2788  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2789  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2790  threadNameSuffix = threadNameSuffix.toLowerCase();
2791  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2792  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2793  Future<Void> future = executor.submit(() -> {
2794  service.closeCaseResources(context);
2795  return null;
2796  });
2797  try {
2798  future.get();
2799  } catch (InterruptedException ex) {
2800  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2801  } catch (CancellationException ex) {
2802  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2803  } catch (ExecutionException ex) {
2804  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2806  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2807  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2808  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2809  }
2810  } finally {
2812  progressIndicator.finish();
2813  }
2814  }
2815  }
2816 
2822  @Messages({
2823  "Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.",
2824  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case."
2825  })
2826  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
2827  String caseDir = metadata.getCaseDirectory();
2828  try {
2829  CoordinationService coordinationService = CoordinationService.getInstance();
2830  caseLock = lockType == CaseLockType.SHARED
2831  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
2832  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
2833  if (caseLock == null) {
2834  if (lockType == CaseLockType.SHARED) {
2835  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
2836  } else {
2837  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
2838  }
2839  }
2840  } catch (InterruptedException | CoordinationServiceException ex) {
2841  if (lockType == CaseLockType.SHARED) {
2842  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
2843  } else {
2844  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
2845  }
2846  }
2847  }
2848 
2852  private void releaseCaseLock() {
2853  if (caseLock != null) {
2854  try {
2855  caseLock.release();
2856  caseLock = null;
2858  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
2859  }
2860  }
2861  }
2862 
2869  private String getOrCreateSubdirectory(String subDirectoryName) {
2870  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2871  if (!subDirectory.exists()) {
2872  subDirectory.mkdirs();
2873  }
2874  return subDirectory.toString();
2875 
2876  }
2877 
2889  @Messages({
2890  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
2891  })
2892  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
2893  boolean errorsOccurred = false;
2894  try {
2895  deleteTextIndex(metadata, progressIndicator);
2896  } catch (KeywordSearchServiceException ex) {
2897  errorsOccurred = true;
2898  logger.log(Level.WARNING, String.format("Failed to delete text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2899  }
2900 
2901  try {
2902  deleteCaseDirectory(metadata, progressIndicator);
2903  } catch (CaseActionException ex) {
2904  errorsOccurred = true;
2905  logger.log(Level.WARNING, String.format("Failed to delete case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2906  }
2907 
2908  deleteFromRecentCases(metadata, progressIndicator);
2909 
2910  if (errorsOccurred) {
2911  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
2912  }
2913  }
2914 
2934  @Messages({
2935  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
2936  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
2937  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
2938  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
2939  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
2940  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
2941  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
2942  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
2943  })
2944  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
2945  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
2946  CoordinationService coordinationService;
2947  try {
2948  coordinationService = CoordinationService.getInstance();
2949  } catch (CoordinationServiceException ex) {
2950  logger.log(Level.SEVERE, String.format("Failed to connect to coordination service when attempting to delete %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2951  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
2952  }
2953 
2954  CaseNodeData caseNodeData;
2955  boolean errorsOccurred = false;
2956  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
2957  if (dirLock == null) {
2958  logger.log(Level.INFO, String.format("Could not delete %s (%s) in %s because a case directory lock was held by another host", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory())); //NON-NLS
2959  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
2960  }
2961 
2962  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
2963  try {
2964  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2965  } catch (CaseNodeDataException | InterruptedException ex) {
2966  logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2967  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
2968  }
2969 
2970  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
2971 
2972  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
2973  try {
2974  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
2975  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
2976  } catch (CoordinationServiceException ex) {
2977  if (!isNoNodeException(ex)) {
2978  errorsOccurred = true;
2979  logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2980  }
2981  } catch (InterruptedException ex) {
2982  logger.log(Level.WARNING, String.format("Error deleting the case resources coordination service node for the case at %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2983  }
2984 
2985  } catch (CoordinationServiceException ex) {
2986  logger.log(Level.SEVERE, String.format("Error exclusively locking the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2987  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
2988  }
2989 
2990  if (!errorsOccurred) {
2991  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
2992  try {
2993  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
2994  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
2995  } catch (CoordinationServiceException | InterruptedException ex) {
2996  logger.log(Level.SEVERE, String.format("Error deleting the case directory lock node for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
2997  errorsOccurred = true;
2998  }
2999  }
3000 
3001  if (errorsOccurred) {
3002  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3003  }
3004  }
3005 
3030  @Beta
3031  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3032  boolean errorsOccurred = false;
3033  try {
3034  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3035  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3036  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3037  deleteFromRecentCases(metadata, progressIndicator);
3038  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3039  errorsOccurred = true;
3040  logger.log(Level.WARNING, String.format("Failed to delete the case database for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3041  } catch (KeywordSearchServiceException ex) {
3042  errorsOccurred = true;
3043  logger.log(Level.WARNING, String.format("Failed to delete the text index for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3044  } catch (CaseActionException ex) {
3045  errorsOccurred = true;
3046  logger.log(Level.WARNING, String.format("Failed to delete the case directory for %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
3047  }
3048  return errorsOccurred;
3049  }
3050 
3071  @Messages({
3072  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3073  })
3074  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3075  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3076  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3077  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3078  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3079  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3080  Class.forName("org.postgresql.Driver"); //NON-NLS
3081  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3082  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3083  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3084  if (queryResult.next()) {
3085  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3086  statement.execute(deleteCommand);
3087  }
3088  }
3089  }
3091  }
3092  }
3093 
3109  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3111  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3112  deleteTextIndex(metadata, progressIndicator);
3114  }
3115  }
3116 
3126  @Messages({
3127  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3128  })
3129  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3130  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3131 
3132  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3133  )) {
3134  searchService.deleteTextIndex(metadata);
3135  }
3136  }
3137 
3152  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3153  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3154  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3155  deleteCaseDirectory(metadata, progressIndicator);
3157  }
3158  }
3159 
3169  @Messages({
3170  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3171  })
3172  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3173  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3174  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3175  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3176  }
3177  }
3178 
3186  @Messages({
3187  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3188  })
3189  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3191  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3192  SwingUtilities.invokeLater(() -> {
3193  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3194  });
3195  }
3196  }
3197 
3208  boolean isNodeNodeEx = false;
3209  Throwable cause = ex.getCause();
3210  if (cause != null) {
3211  String causeMessage = cause.getMessage();
3212  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3213  }
3214  return isNodeNodeEx;
3215  }
3216 
3228  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3229  try {
3230  caseNodeData.setDeletedFlag(flag);
3231  CaseNodeData.writeCaseNodeData(caseNodeData);
3232  } catch (CaseNodeDataException ex) {
3233  logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
3234 
3235  }
3236  }
3237 
3246  private interface CaseAction<T, V, R> {
3247 
3258  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3259  }
3260 
3265  private enum CaseLockType {
3266  SHARED, EXCLUSIVE;
3267  }
3268 
3273  @ThreadSafe
3274  private final static class CancelButtonListener implements ActionListener {
3275 
3276  private final String cancellationMessage;
3277  @GuardedBy("this")
3278  private boolean cancelRequested;
3279  @GuardedBy("this")
3281  @GuardedBy("this")
3282  private Future<?> caseActionFuture;
3283 
3292  private CancelButtonListener(String cancellationMessage) {
3293  this.cancellationMessage = cancellationMessage;
3294  }
3295 
3301  private synchronized void setCaseContext(CaseContext caseContext) {
3302  this.caseContext = caseContext;
3303  /*
3304  * If the cancel button has already been pressed, pass the
3305  * cancellation on to the case context.
3306  */
3307  if (cancelRequested) {
3308  cancel();
3309  }
3310  }
3311 
3317  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3318  this.caseActionFuture = caseActionFuture;
3319  /*
3320  * If the cancel button has already been pressed, cancel the Future
3321  * of the task.
3322  */
3323  if (cancelRequested) {
3324  cancel();
3325  }
3326  }
3327 
3333  @Override
3334  public synchronized void actionPerformed(ActionEvent event) {
3335  cancel();
3336  }
3337 
3341  private void cancel() {
3342  /*
3343  * At a minimum, set the cancellation requested flag of this
3344  * listener.
3345  */
3346  this.cancelRequested = true;
3347  if (null != this.caseContext) {
3348  /*
3349  * Set the cancellation request flag and display the
3350  * cancellation message in the progress indicator for the case
3351  * context associated with this listener.
3352  */
3354  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3355  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3356  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3357  }
3358  }
3359  this.caseContext.requestCancel();
3360  }
3361  if (null != this.caseActionFuture) {
3362  /*
3363  * Cancel the Future of the task associated with this listener.
3364  * Note that the task thread will be interrupted if the task is
3365  * blocked.
3366  */
3367  this.caseActionFuture.cancel(true);
3368  }
3369  }
3370  }
3371 
3375  private static class TaskThreadFactory implements ThreadFactory {
3376 
3377  private final String threadName;
3378 
3379  private TaskThreadFactory(String threadName) {
3380  this.threadName = threadName;
3381  }
3382 
3383  @Override
3384  public Thread newThread(Runnable task) {
3385  return new Thread(task, threadName);
3386  }
3387 
3388  }
3389 
3397  @Deprecated
3398  public static String getAppName() {
3399  return UserPreferences.getAppName();
3400  }
3401 
3421  @Deprecated
3422  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3423  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3424  }
3425 
3446  @Deprecated
3447  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3448  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3449  }
3450 
3462  @Deprecated
3463  public static void open(String caseMetadataFilePath) throws CaseActionException {
3464  openAsCurrentCase(caseMetadataFilePath);
3465  }
3466 
3476  @Deprecated
3477  public void closeCase() throws CaseActionException {
3478  closeCurrentCase();
3479  }
3480 
3486  @Deprecated
3487  public static void invokeStartupDialog() {
3489  }
3490 
3504  @Deprecated
3505  public static String convertTimeZone(String timeZoneId) {
3506  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3507  }
3508 
3518  @Deprecated
3519  public static boolean pathExists(String filePath) {
3520  return new File(filePath).isFile();
3521  }
3522 
3531  @Deprecated
3532  public static String getAutopsyVersion() {
3533  return Version.getVersion();
3534  }
3535 
3543  @Deprecated
3544  public static boolean existsCurrentCase() {
3545  return isCaseOpen();
3546  }
3547 
3557  @Deprecated
3558  public static String getModulesOutputDirRelPath() {
3559  return "ModuleOutput"; //NON-NLS
3560  }
3561 
3571  @Deprecated
3572  public static PropertyChangeSupport
3574  return new PropertyChangeSupport(Case.class
3575  );
3576  }
3577 
3586  @Deprecated
3587  public String getModulesOutputDirAbsPath() {
3588  return getModuleDirectory();
3589  }
3590 
3605  @Deprecated
3606  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
3607  try {
3608  Image newDataSource = caseDb.getImageById(imgId);
3609  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
3610  return newDataSource;
3611  } catch (TskCoreException ex) {
3612  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
3613  }
3614  }
3615 
3623  @Deprecated
3624  public Set<TimeZone> getTimeZone() {
3625  return getTimeZones();
3626  }
3627 
3638  @Deprecated
3639  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
3640  deleteReports(reports);
3641  }
3642 
3643 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:159
List< Content > getDataSources()
Definition: Case.java:1428
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1566
Case(CaseMetadata caseMetaData)
Definition: Case.java:1786
static CaseType fromString(String typeName)
Definition: Case.java:202
final SleuthkitEventListener sleuthkitEventListener
Definition: Case.java:170
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:155
static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag)
Definition: Case.java:3228
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:608
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1631
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:1981
CoordinationService.Lock caseLock
Definition: Case.java:168
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3031
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:3606
static synchronized IngestManager getInstance()
void deleteNode(CategoryNode category, String nodePath)
static final ExecutorService openFileSystemsExecutor
Definition: Case.java:163
static final String NO_NODE_ERROR_MSG_FRAGMENT
Definition: Case.java:157
void acquireCaseLock(CaseLockType lockType)
Definition: Case.java:2826
static boolean existsCurrentCase()
Definition: Case.java:3544
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:466
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:158
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List< BlackboardArtifactTag > removedTagList)
Definition: Case.java:1620
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:161
static final String EXPORT_FOLDER
Definition: Case.java:150
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:950
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1577
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1555
static volatile Frame mainFrame
Definition: Case.java:164
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3505
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:2892
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event)
Definition: Case.java:424
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
volatile ExecutorService caseActionExecutor
Definition: Case.java:167
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1592
static final String CACHE_FOLDER
Definition: Case.java:149
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3152
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1777
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1646
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1068
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1531
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3447
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:145
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:147
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:812
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:3639
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3301
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:146
static boolean isValidName(String caseName)
Definition: Case.java:550
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2450
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2267
CollaborationMonitor collaborationMonitor
Definition: Case.java:171
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2617
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3558
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2308
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3109
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:1827
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3334
static final String MODULE_FOLDER
Definition: Case.java:154
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2478
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3422
synchronized void openRemoteEventChannel(String channelName)
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2332
void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event)
Definition: Case.java:430
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:910
static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3189
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:635
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:526
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static boolean isNoNodeException(CoordinationServiceException ex)
Definition: Case.java:3207
void fireModuleDataEvent(ModuleDataEvent moduleDataEvent)
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2721
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1607
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3573
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:454
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3317
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2404
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:516
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2288
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3129
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2379
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1701
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1518
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1664
static boolean pathExists(String filePath)
Definition: Case.java:3519
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2212
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3463
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:861
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1042
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2869
boolean equalsName(String otherTypeName)
Definition: Case.java:260
static final String EVENT_CHANNEL_NAME
Definition: Case.java:148
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:491
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:151
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:1929
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:165
void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase)
Definition: Case.java:2507
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1489
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:506
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1542
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:156
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:581
static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3172
static final String CASE_TEMP_DIR
Definition: Case.java:144
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1503
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:2944
static final String CONFIG_FOLDER
Definition: Case.java:153
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:536
static final Object caseActionSerializationLock
Definition: Case.java:160
static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3074
static final String REPORTS_FOLDER
Definition: Case.java:152
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:481
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2358
static synchronized IngestServices getInstance()

Copyright © 2012-2020 Basis Technology. Generated on: Tue Sep 22 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.