Autopsy  4.17.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  closedCase.doCloseCaseAction();
724  currentCase = null;
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  String query = "SELECT time_zone FROM data_source_info";
1440  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1441  ResultSet timeZoneSet = dbQuery.getResultSet();
1442  while (timeZoneSet.next()) {
1443  String timeZone = timeZoneSet.getString("time_zone");
1444  if (timeZone != null && !timeZone.isEmpty()) {
1445  timezones.add(TimeZone.getTimeZone(timeZone));
1446  }
1447  }
1448  } catch (TskCoreException | SQLException ex) {
1449  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1450  }
1451  return timezones;
1452  }
1453 
1460  public String getTextIndexName() {
1461  return getMetadata().getTextIndexName();
1462  }
1463 
1470  public boolean hasData() {
1471  boolean hasDataSources = false;
1472  String query = "SELECT count(*) AS count FROM data_source_info";
1473  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1474  ResultSet resultSet = dbQuery.getResultSet();
1475  if (resultSet.next()) {
1476  long numDataSources = resultSet.getLong("count");
1477  if (numDataSources > 0) {
1478  hasDataSources = true;
1479  }
1480  }
1481  } catch (TskCoreException | SQLException ex) {
1482  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1483  }
1484  return hasDataSources;
1485  }
1486 
1497  public void notifyAddingDataSource(UUID eventId) {
1498  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1499  }
1500 
1511  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1512  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1513  }
1514 
1526  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1527  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1528  }
1529 
1539  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1540  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1541  }
1542 
1550  public void notifyContentTagAdded(ContentTag newTag) {
1551  notifyContentTagAdded(newTag, null);
1552  }
1553 
1563  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1564  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1565  }
1566 
1574  public void notifyContentTagDeleted(ContentTag deletedTag) {
1575  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1576  }
1577 
1585  public void notifyTagDefinitionChanged(String changedTagName) {
1586  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1587  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1588  }
1589 
1600  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1601  try {
1602  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1603  } catch (NoCurrentCaseException ex) {
1604  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1605  }
1606  }
1607 
1615  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1616  notifyBlackBoardArtifactTagAdded(newTag, null);
1617  }
1618 
1628  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1629  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1630  }
1631 
1639  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1640  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1641  }
1642 
1654  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1655  addReport(localPath, srcModuleName, reportName, null);
1656  }
1657 
1672  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1673  String normalizedLocalPath;
1674  try {
1675  if (localPath.toLowerCase().contains("http:")) {
1676  normalizedLocalPath = localPath;
1677  } else {
1678  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1679  }
1680  } catch (InvalidPathException ex) {
1681  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1682  throw new TskCoreException(errorMsg, ex);
1683  }
1684  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1685  eventPublisher.publish(new ReportAddedEvent(report));
1686  return report;
1687  }
1688 
1697  public List<Report> getAllReports() throws TskCoreException {
1698  return this.caseDb.getAllReports();
1699  }
1700 
1709  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1710  for (Report report : reports) {
1711  this.caseDb.deleteReport(report);
1712  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1713  }
1714  }
1715 
1721  CaseMetadata getMetadata() {
1722  return metadata;
1723  }
1724 
1732  @Messages({
1733  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1734  })
1735  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1736  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1737  try {
1738  metadata.setCaseDetails(caseDetails);
1739  } catch (CaseMetadataException ex) {
1740  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1741  }
1742  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1743  try {
1744  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
1745  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
1746  CaseNodeData.writeCaseNodeData(nodeData);
1747  } catch (CaseNodeDataException | InterruptedException ex) {
1748  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
1749  }
1750  }
1751  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1752  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1753  }
1754  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1755  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1756  }
1757  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1758  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1759  }
1760  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1761  if (RuntimeProperties.runningWithGUI()) {
1762  SwingUtilities.invokeLater(() -> {
1763  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1764  try {
1765  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1766  } catch (Exception ex) {
1767  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1768  }
1769  });
1770  }
1771  }
1772 
1785  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1786  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
1787  }
1788 
1794  private Case(CaseMetadata caseMetaData) {
1795  metadata = caseMetaData;
1796  sleuthkitEventListener = new SleuthkitEventListener();
1797  }
1798 
1828  @Messages({
1829  "Case.progressIndicatorCancelButton.label=Cancel",
1830  "Case.progressMessage.preparing=Preparing...",
1831  "Case.progressMessage.cancelling=Cancelling...",
1832  "Case.exceptionMessage.cancelled=Cancelled.",
1833  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1834  })
1835  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
1836  /*
1837  * Create and start either a GUI progress indicator (with or without a
1838  * cancel button) or a logging progress indicator.
1839  */
1840  CancelButtonListener cancelButtonListener = null;
1841  ProgressIndicator progressIndicator;
1843  if (allowCancellation) {
1844  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1845  progressIndicator = new ModalDialogProgressIndicator(
1846  mainFrame,
1847  progressIndicatorTitle,
1848  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1849  Bundle.Case_progressIndicatorCancelButton_label(),
1850  cancelButtonListener);
1851  } else {
1852  progressIndicator = new ModalDialogProgressIndicator(
1853  mainFrame,
1854  progressIndicatorTitle);
1855  }
1856  } else {
1857  progressIndicator = new LoggingProgressIndicator();
1858  }
1859  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1860 
1861  /*
1862  * Do the case action in the single thread in the case action executor.
1863  * If the case is a multi-user case, a case lock is acquired and held
1864  * until explictly released and an exclusive case resources lock is
1865  * aquired and held for the duration of the action.
1866  */
1867  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1868  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
1869  Future<Void> future = caseActionExecutor.submit(() -> {
1870  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1871  caseAction.execute(progressIndicator, additionalParams);
1872  } else {
1873  acquireCaseLock(caseLockType);
1874  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
1875  if (null == resourcesLock) {
1876  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
1877  }
1878  caseAction.execute(progressIndicator, additionalParams);
1879  } catch (CaseActionException ex) {
1880  releaseCaseLock();
1881  throw ex;
1882  }
1883  }
1884  return null;
1885  });
1886  if (null != cancelButtonListener) {
1887  cancelButtonListener.setCaseActionFuture(future);
1888  }
1889 
1890  /*
1891  * Wait for the case action task to finish.
1892  */
1893  try {
1894  future.get();
1895  } catch (InterruptedException discarded) {
1896  /*
1897  * The thread this method is running in has been interrupted.
1898  */
1899  if (null != cancelButtonListener) {
1900  cancelButtonListener.actionPerformed(null);
1901  } else {
1902  future.cancel(true);
1903  }
1904  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1905  } catch (CancellationException discarded) {
1906  /*
1907  * The case action has been cancelled.
1908  */
1909  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1910  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1911  } catch (ExecutionException ex) {
1912  /*
1913  * The case action has thrown an exception.
1914  */
1915  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1916  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1917  } finally {
1918  progressIndicator.finish();
1919  }
1920  }
1921 
1937  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
1938  assert (additionalParams == null);
1939  try {
1941  createCaseDirectoryIfDoesNotExist(progressIndicator);
1943  switchLoggingToCaseLogsDirectory(progressIndicator);
1945  saveCaseMetadataToFile(progressIndicator);
1947  createCaseNodeData(progressIndicator);
1950  createCaseDatabase(progressIndicator);
1952  openCaseLevelServices(progressIndicator);
1954  openAppServiceCaseResources(progressIndicator, true);
1956  openCommunicationChannels(progressIndicator);
1957  return null;
1958 
1959  } catch (CaseActionException ex) {
1960  /*
1961  * Cancellation or failure. The sleep is a little hack to clear the
1962  * interrupted flag for this thread if this is a cancellation
1963  * scenario, so that the clean up can run to completion in the
1964  * current thread.
1965  */
1966  try {
1967  Thread.sleep(1);
1968  } catch (InterruptedException discarded) {
1969  }
1970  close(progressIndicator);
1971  throw ex;
1972  }
1973  }
1974 
1989  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
1990  assert (additionalParams == null);
1991  try {
1993  switchLoggingToCaseLogsDirectory(progressIndicator);
1995  updateCaseNodeData(progressIndicator);
1997  deleteTempfilesFromCaseDirectory(progressIndicator);
1999  openCaseDataBase(progressIndicator);
2001  openCaseLevelServices(progressIndicator);
2003  openAppServiceCaseResources(progressIndicator, false);
2005  openCommunicationChannels(progressIndicator);
2008  return null;
2009 
2010  } catch (CaseActionException ex) {
2011  /*
2012  * Cancellation or failure. The sleep is a little hack to clear the
2013  * interrupted flag for this thread if this is a cancellation
2014  * scenario, so that the clean up can run to completion in the
2015  * current thread.
2016  */
2017  try {
2018  Thread.sleep(1);
2019  } catch (InterruptedException discarded) {
2020  }
2021  close(progressIndicator);
2022  throw ex;
2023  }
2024  }
2025 
2035  @Messages({
2036  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2037  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2038  })
2040  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2041  backgroundOpenFileSystemsFuture.cancel(true);
2042  }
2043 
2045  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2046  }
2047 
2052  private static class BackgroundOpenFileSystemsTask implements Runnable {
2053 
2054  private final SleuthkitCase tskCase;
2055  private final String caseName;
2056  private final long MAX_IMAGE_THRESHOLD = 100;
2058 
2067  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2068  this.tskCase = tskCase;
2069  this.progressIndicator = progressIndicator;
2070  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2071  }
2072 
2080  private void checkIfCancelled() throws InterruptedException {
2081  if (Thread.interrupted()) {
2082  throw new InterruptedException();
2083  }
2084  }
2085 
2091  private List<Image> getImages() {
2092  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2093  try {
2094  return this.tskCase.getImages();
2095  } catch (TskCoreException ex) {
2096  logger.log(
2097  Level.SEVERE,
2098  String.format("Could not obtain images while opening case: %s.", caseName),
2099  ex);
2100 
2101  return null;
2102  }
2103  }
2104 
2114  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2115  byte[] tempBuff = new byte[512];
2116 
2117  for (Image image : images) {
2118  String imageStr = image.getName();
2119 
2120  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2121 
2122  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2123  checkIfCancelled();
2124  for (FileSystem fileSystem : fileSystems) {
2125  fileSystem.read(tempBuff, 0, 512);
2126  checkIfCancelled();
2127  }
2128 
2129  }
2130  }
2131 
2132  @Override
2133  public void run() {
2134  try {
2135  checkIfCancelled();
2136  List<Image> images = getImages();
2137  if (images == null) {
2138  return;
2139  }
2140 
2141  if (images.size() > MAX_IMAGE_THRESHOLD) {
2142  // If we have a large number of images, don't try to preload anything
2143  logger.log(
2144  Level.INFO,
2145  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2146  return;
2147  }
2148 
2149  checkIfCancelled();
2150  openFileSystems(images);
2151  } catch (InterruptedException ex) {
2152  logger.log(
2153  Level.INFO,
2154  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2155  } catch (Exception ex) {
2156  // Exception firewall
2157  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2158  }
2159  }
2160 
2161  }
2162 
2177  @Messages({
2178  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2179  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2180  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2181  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2182  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2183  assert (additionalParams instanceof Long);
2184  open(progressIndicator, null);
2185  try {
2186  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2187  Long dataSourceObjectID = (Long) additionalParams;
2188  try {
2189  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2190  if (dataSource == null) {
2191  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2192  }
2193  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2194  } catch (TskDataException | TskCoreException ex) {
2195  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2196  }
2197  try {
2198  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2199  } catch (KeywordSearchServiceException ex) {
2200  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2201  }
2202  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2203  return null;
2204  } finally {
2205  close(progressIndicator);
2206  releaseCaseLock();
2207  }
2208  }
2209 
2220  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2221 
2222  if (portableCaseFolder.exists()) {
2223  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2224  }
2225  if (!portableCaseFolder.mkdirs()) {
2226  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2227  }
2228 
2229  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2231  try {
2232  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2233  caseName, details, metadata);
2234  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2235  } catch (CaseMetadataException ex) {
2236  throw new TskCoreException("Error creating case metadata", ex);
2237  }
2238 
2239  // Create the Sleuthkit case
2240  SleuthkitCase portableSleuthkitCase;
2241  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2242  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2243 
2244  return portableSleuthkitCase;
2245  }
2246 
2257  if (Thread.currentThread().isInterrupted()) {
2258  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2259  }
2260  }
2261 
2272  @Messages({
2273  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2274  })
2275  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2276  /*
2277  * TODO (JIRA-2180): Always create the case directory as part of the
2278  * case creation process.
2279  */
2280  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2281  if (new File(metadata.getCaseDirectory()).exists() == false) {
2282  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2283  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2284  }
2285  }
2286 
2293  @Messages({
2294  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2295  })
2296  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2297  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2299  }
2300 
2312  @Messages({
2313  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2314  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2315  })
2316  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2317  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2318  try {
2319  this.metadata.writeToFile();
2320  } catch (CaseMetadataException ex) {
2321  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2322  }
2323  }
2324 
2336  @Messages({
2337  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2338  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2339  })
2340  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2342  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2343  try {
2344  CaseNodeData.createCaseNodeData(metadata);
2345  } catch (CaseNodeDataException | InterruptedException ex) {
2346  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2347  }
2348  }
2349  }
2350 
2362  @Messages({
2363  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2364  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2365  })
2366  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2368  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2369  try {
2371  nodeData.setLastAccessDate(new Date());
2372  CaseNodeData.writeCaseNodeData(nodeData);
2373  } catch (CaseNodeDataException | InterruptedException ex) {
2374  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2375  }
2376  }
2377  }
2378 
2384  @Messages({
2385  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2386  })
2387  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2388  /*
2389  * Clear the temp subdirectory of the case directory.
2390  */
2391  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2392  FileUtil.deleteDir(new File(this.getTempDirectory()));
2393  }
2394 
2406  @Messages({
2407  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2408  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2409  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2410  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2411  })
2412  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2413  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2414  try {
2415  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2416  /*
2417  * For single-user cases, the case database is a SQLite database
2418  * with a standard name, physically located in the case
2419  * directory.
2420  */
2421  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2422  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2423  } else {
2424  /*
2425  * For multi-user cases, the case database is a PostgreSQL
2426  * database with a name derived from the case display name,
2427  * physically located on the PostgreSQL database server.
2428  */
2429  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2430  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2431  }
2432  } catch (TskCoreException ex) {
2433  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2434  } catch (UserPreferencesException ex) {
2435  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2436  } catch (CaseMetadataException ex) {
2437  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2438  }
2439  }
2440 
2452  @Messages({
2453  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2454  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2455  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2456  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2457  })
2458  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2459  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2460  try {
2461  String databaseName = metadata.getCaseDatabaseName();
2462  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2463  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
2465  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2466  } else {
2467  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2468  }
2469  } catch (TskUnsupportedSchemaVersionException ex) {
2470  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2471  } catch (UserPreferencesException ex) {
2472  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2473  } catch (TskCoreException ex) {
2474  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2475  }
2476  }
2477 
2484  @Messages({
2485  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2486  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2487  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2488  this.caseServices = new Services(caseDb);
2489  /*
2490  * RC Note: JM put this initialization here. I'm not sure why. However,
2491  * my attempt to put it in the openCaseDatabase method seems to lead to
2492  * intermittent unchecked exceptions concerning a missing subscriber.
2493  */
2494  caseDb.registerForEvents(sleuthkitEventListener);
2495  }
2496 
2509  @NbBundle.Messages({
2510  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2511  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2512  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2513  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2514  })
2515  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2516  /*
2517  * Each service gets its own independently cancellable/interruptible
2518  * task, running in a named thread managed by an executor service, with
2519  * its own progress indicator. This allows for cancellation of the
2520  * opening of case resources for individual services. It also makes it
2521  * possible to ensure that each service task completes before the next
2522  * one starts by awaiting termination of the executor service.
2523  */
2524  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2525 
2526  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2527  )) {
2528  /*
2529  * Create a progress indicator for the task and start the task. If
2530  * running with a GUI, the progress indicator will be a dialog box
2531  * with a Cancel button.
2532  */
2533  CancelButtonListener cancelButtonListener = null;
2534  ProgressIndicator appServiceProgressIndicator;
2536  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2537  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2538  mainFrame,
2539  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2540  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2541  Bundle.Case_progressIndicatorCancelButton_label(),
2542  cancelButtonListener);
2543  } else {
2544  appServiceProgressIndicator = new LoggingProgressIndicator();
2545  }
2546  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2547  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2548  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2549  threadNameSuffix = threadNameSuffix.toLowerCase();
2550  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2551  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2552  Future<Void> future = executor.submit(() -> {
2553  service.openCaseResources(context);
2554  return null;
2555  });
2556  if (null != cancelButtonListener) {
2557  cancelButtonListener.setCaseContext(context);
2558  cancelButtonListener.setCaseActionFuture(future);
2559  }
2560 
2561  /*
2562  * Wait for the task to either be completed or
2563  * cancelled/interrupted, or for the opening of the case to be
2564  * cancelled.
2565  */
2566  try {
2567  future.get();
2568  } catch (InterruptedException discarded) {
2569  /*
2570  * The parent create/open case task has been cancelled.
2571  */
2572  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()));
2573  future.cancel(true);
2574  } catch (CancellationException discarded) {
2575  /*
2576  * The opening of case resources by the application service has
2577  * been cancelled, so the executor service has thrown. Note that
2578  * there is no guarantee the task itself has responded to the
2579  * cancellation request yet.
2580  */
2581  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()));
2582  } catch (ExecutionException ex) {
2583  /*
2584  * An exception was thrown while executing the task. The
2585  * case-specific application service resources are not
2586  * essential. Log an error and notify the user if running the
2587  * desktop GUI, but do not throw.
2588  */
2589  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2591  SwingUtilities.invokeLater(() -> {
2592  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2593  });
2594  }
2595  } finally {
2596  /*
2597  * Shut down the executor service and wait for it to finish.
2598  * This ensures that the task has finished. Without this, it
2599  * would be possible to start the next task before the current
2600  * task responded to a cancellation request.
2601  */
2603  appServiceProgressIndicator.finish();
2604  }
2606  }
2607  }
2608 
2620  @Messages({
2621  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2622  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2623  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2624  })
2625  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2626  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2627  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2628  try {
2629  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2631  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2632  } catch (AutopsyEventException ex) {
2633  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2634  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2635  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2636  }
2637  }
2638  }
2639 
2654  private void doCloseCaseAction() throws CaseActionException {
2655  /*
2656  * Set up either a GUI progress indicator without a Cancel button or a
2657  * logging progress indicator.
2658  */
2659  ProgressIndicator progressIndicator;
2661  progressIndicator = new ModalDialogProgressIndicator(
2662  mainFrame,
2663  Bundle.Case_progressIndicatorTitle_closingCase());
2664  } else {
2665  progressIndicator = new LoggingProgressIndicator();
2666  }
2667  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2668 
2669  /*
2670  * Closing a case is always done in the same non-UI thread that
2671  * opened/created the case. If the case is a multi-user case, this
2672  * ensures that case lock that is held as long as the case is open is
2673  * released in the same thread in which it was acquired, as is required
2674  * by the coordination service.
2675  */
2676  Future<Void> future = caseActionExecutor.submit(() -> {
2677  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2678  close(progressIndicator);
2679  } else {
2680  /*
2681  * Acquire an exclusive case resources lock to ensure only one
2682  * node at a time can create/open/upgrade/close the case
2683  * resources.
2684  */
2685  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2686  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2687  if (null == resourcesLock) {
2688  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2689  }
2690  close(progressIndicator);
2691  } finally {
2692  /*
2693  * Always release the case directory lock that was acquired
2694  * when the case was opened.
2695  */
2696  releaseCaseLock();
2697  }
2698  }
2699  return null;
2700  });
2701 
2702  try {
2703  future.get();
2704  } catch (InterruptedException | CancellationException unused) {
2705  /*
2706  * The wait has been interrupted by interrupting the thread running
2707  * this method. Not allowing cancellation of case closing, so ignore
2708  * the interrupt. Likewise, cancellation of the case closing task is
2709  * not supported.
2710  */
2711  } catch (ExecutionException ex) {
2712  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2713  } finally {
2714  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2715  progressIndicator.finish();
2716  }
2717  }
2718 
2724  @Messages({
2725  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2726  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2727  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2728  })
2729  private void close(ProgressIndicator progressIndicator) {
2731 
2732  /*
2733  * Stop sending/receiving case events to and from other nodes if this is
2734  * a multi-user case.
2735  */
2736  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2737  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2738  if (null != collaborationMonitor) {
2739  collaborationMonitor.shutdown();
2740  }
2741  eventPublisher.closeRemoteEventChannel();
2742  }
2743 
2744  /*
2745  * Allow all registered application services providers to close
2746  * resources related to the case.
2747  */
2748  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2750 
2751  /*
2752  * Close the case database.
2753  */
2754  if (null != caseDb) {
2755  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2756  caseDb.unregisterForEvents(sleuthkitEventListener);
2757  caseDb.close();
2758  }
2759 
2763  deleteTempfilesFromCaseDirectory(progressIndicator);
2764 
2765  /*
2766  * Switch the log directory.
2767  */
2768  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2770  }
2771 
2776  @Messages({
2777  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2778  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2779  })
2781  /*
2782  * Each service gets its own independently cancellable task, and thus
2783  * its own task progress indicator.
2784  */
2785  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2786  )) {
2787  ProgressIndicator progressIndicator;
2789  progressIndicator = new ModalDialogProgressIndicator(
2790  mainFrame,
2791  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2792  } else {
2793  progressIndicator = new LoggingProgressIndicator();
2794  }
2795  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2796  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2797  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2798  threadNameSuffix = threadNameSuffix.toLowerCase();
2799  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2800  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2801  Future<Void> future = executor.submit(() -> {
2802  service.closeCaseResources(context);
2803  return null;
2804  });
2805  try {
2806  future.get();
2807  } catch (InterruptedException ex) {
2808  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2809  } catch (CancellationException ex) {
2810  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2811  } catch (ExecutionException ex) {
2812  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2814  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2815  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2816  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2817  }
2818  } finally {
2820  progressIndicator.finish();
2821  }
2822  }
2823  }
2824 
2830  @Messages({
2831  "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.",
2832  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case."
2833  })
2834  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
2835  String caseDir = metadata.getCaseDirectory();
2836  try {
2837  CoordinationService coordinationService = CoordinationService.getInstance();
2838  caseLock = lockType == CaseLockType.SHARED
2839  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
2840  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
2841  if (caseLock == null) {
2842  if (lockType == CaseLockType.SHARED) {
2843  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
2844  } else {
2845  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
2846  }
2847  }
2848  } catch (InterruptedException | CoordinationServiceException ex) {
2849  if (lockType == CaseLockType.SHARED) {
2850  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
2851  } else {
2852  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
2853  }
2854  }
2855  }
2856 
2860  private void releaseCaseLock() {
2861  if (caseLock != null) {
2862  try {
2863  caseLock.release();
2864  caseLock = null;
2866  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
2867  }
2868  }
2869  }
2870 
2877  private String getOrCreateSubdirectory(String subDirectoryName) {
2878  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2879  if (!subDirectory.exists()) {
2880  subDirectory.mkdirs();
2881  }
2882  return subDirectory.toString();
2883 
2884  }
2885 
2897  @Messages({
2898  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
2899  })
2900  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
2901  boolean errorsOccurred = false;
2902  try {
2903  deleteTextIndex(metadata, progressIndicator);
2904  } catch (KeywordSearchServiceException ex) {
2905  errorsOccurred = true;
2906  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
2907  }
2908 
2909  try {
2910  deleteCaseDirectory(metadata, progressIndicator);
2911  } catch (CaseActionException ex) {
2912  errorsOccurred = true;
2913  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
2914  }
2915 
2916  deleteFromRecentCases(metadata, progressIndicator);
2917 
2918  if (errorsOccurred) {
2919  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
2920  }
2921  }
2922 
2942  @Messages({
2943  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
2944  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
2945  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
2946  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
2947  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
2948  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
2949  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
2950  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
2951  })
2952  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
2953  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
2954  CoordinationService coordinationService;
2955  try {
2956  coordinationService = CoordinationService.getInstance();
2957  } catch (CoordinationServiceException ex) {
2958  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
2959  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
2960  }
2961 
2962  CaseNodeData caseNodeData;
2963  boolean errorsOccurred = false;
2964  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
2965  if (dirLock == null) {
2966  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
2967  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
2968  }
2969 
2970  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
2971  try {
2972  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2973  } catch (CaseNodeDataException | InterruptedException ex) {
2974  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
2975  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
2976  }
2977 
2978  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
2979 
2980  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
2981  try {
2982  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
2983  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
2984  } catch (CoordinationServiceException ex) {
2985  if (!isNoNodeException(ex)) {
2986  errorsOccurred = true;
2987  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
2988  }
2989  } catch (InterruptedException ex) {
2990  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
2991  }
2992 
2993  } catch (CoordinationServiceException ex) {
2994  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
2995  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
2996  }
2997 
2998  if (!errorsOccurred) {
2999  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3000  try {
3001  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3002  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3003  } catch (CoordinationServiceException | InterruptedException ex) {
3004  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
3005  errorsOccurred = true;
3006  }
3007  }
3008 
3009  if (errorsOccurred) {
3010  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3011  }
3012  }
3013 
3038  @Beta
3039  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3040  boolean errorsOccurred = false;
3041  try {
3042  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3043  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3044  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3045  deleteFromRecentCases(metadata, progressIndicator);
3046  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3047  errorsOccurred = true;
3048  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
3049  } catch (KeywordSearchServiceException ex) {
3050  errorsOccurred = true;
3051  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
3052  } catch (CaseActionException ex) {
3053  errorsOccurred = true;
3054  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
3055  }
3056  return errorsOccurred;
3057  }
3058 
3079  @Messages({
3080  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3081  })
3082  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3083  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3084  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3085  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3086  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3087  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3088  Class.forName("org.postgresql.Driver"); //NON-NLS
3089  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3090  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3091  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3092  if (queryResult.next()) {
3093  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3094  statement.execute(deleteCommand);
3095  }
3096  }
3097  }
3099  }
3100  }
3101 
3117  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3119  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3120  deleteTextIndex(metadata, progressIndicator);
3122  }
3123  }
3124 
3134  @Messages({
3135  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3136  })
3137  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3138  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3139 
3140  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3141  )) {
3142  searchService.deleteTextIndex(metadata);
3143  }
3144  }
3145 
3160  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3161  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3162  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3163  deleteCaseDirectory(metadata, progressIndicator);
3165  }
3166  }
3167 
3177  @Messages({
3178  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3179  })
3180  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3181  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3182  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3183  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3184  }
3185  }
3186 
3194  @Messages({
3195  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3196  })
3197  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3199  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3200  SwingUtilities.invokeLater(() -> {
3201  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3202  });
3203  }
3204  }
3205 
3216  boolean isNodeNodeEx = false;
3217  Throwable cause = ex.getCause();
3218  if (cause != null) {
3219  String causeMessage = cause.getMessage();
3220  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3221  }
3222  return isNodeNodeEx;
3223  }
3224 
3236  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3237  try {
3238  caseNodeData.setDeletedFlag(flag);
3239  CaseNodeData.writeCaseNodeData(caseNodeData);
3240  } catch (CaseNodeDataException ex) {
3241  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);
3242 
3243  }
3244  }
3245 
3254  private interface CaseAction<T, V, R> {
3255 
3266  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3267  }
3268 
3273  private enum CaseLockType {
3274  SHARED, EXCLUSIVE;
3275  }
3276 
3281  @ThreadSafe
3282  private final static class CancelButtonListener implements ActionListener {
3283 
3284  private final String cancellationMessage;
3285  @GuardedBy("this")
3286  private boolean cancelRequested;
3287  @GuardedBy("this")
3289  @GuardedBy("this")
3290  private Future<?> caseActionFuture;
3291 
3300  private CancelButtonListener(String cancellationMessage) {
3301  this.cancellationMessage = cancellationMessage;
3302  }
3303 
3309  private synchronized void setCaseContext(CaseContext caseContext) {
3310  this.caseContext = caseContext;
3311  /*
3312  * If the cancel button has already been pressed, pass the
3313  * cancellation on to the case context.
3314  */
3315  if (cancelRequested) {
3316  cancel();
3317  }
3318  }
3319 
3325  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3326  this.caseActionFuture = caseActionFuture;
3327  /*
3328  * If the cancel button has already been pressed, cancel the Future
3329  * of the task.
3330  */
3331  if (cancelRequested) {
3332  cancel();
3333  }
3334  }
3335 
3341  @Override
3342  public synchronized void actionPerformed(ActionEvent event) {
3343  cancel();
3344  }
3345 
3349  private void cancel() {
3350  /*
3351  * At a minimum, set the cancellation requested flag of this
3352  * listener.
3353  */
3354  this.cancelRequested = true;
3355  if (null != this.caseContext) {
3356  /*
3357  * Set the cancellation request flag and display the
3358  * cancellation message in the progress indicator for the case
3359  * context associated with this listener.
3360  */
3362  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3363  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3364  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3365  }
3366  }
3367  this.caseContext.requestCancel();
3368  }
3369  if (null != this.caseActionFuture) {
3370  /*
3371  * Cancel the Future of the task associated with this listener.
3372  * Note that the task thread will be interrupted if the task is
3373  * blocked.
3374  */
3375  this.caseActionFuture.cancel(true);
3376  }
3377  }
3378  }
3379 
3383  private static class TaskThreadFactory implements ThreadFactory {
3384 
3385  private final String threadName;
3386 
3387  private TaskThreadFactory(String threadName) {
3388  this.threadName = threadName;
3389  }
3390 
3391  @Override
3392  public Thread newThread(Runnable task) {
3393  return new Thread(task, threadName);
3394  }
3395 
3396  }
3397 
3405  @Deprecated
3406  public static String getAppName() {
3407  return UserPreferences.getAppName();
3408  }
3409 
3429  @Deprecated
3430  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3431  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3432  }
3433 
3454  @Deprecated
3455  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3456  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3457  }
3458 
3470  @Deprecated
3471  public static void open(String caseMetadataFilePath) throws CaseActionException {
3472  openAsCurrentCase(caseMetadataFilePath);
3473  }
3474 
3484  @Deprecated
3485  public void closeCase() throws CaseActionException {
3486  closeCurrentCase();
3487  }
3488 
3494  @Deprecated
3495  public static void invokeStartupDialog() {
3497  }
3498 
3512  @Deprecated
3513  public static String convertTimeZone(String timeZoneId) {
3514  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3515  }
3516 
3526  @Deprecated
3527  public static boolean pathExists(String filePath) {
3528  return new File(filePath).isFile();
3529  }
3530 
3539  @Deprecated
3540  public static String getAutopsyVersion() {
3541  return Version.getVersion();
3542  }
3543 
3551  @Deprecated
3552  public static boolean existsCurrentCase() {
3553  return isCaseOpen();
3554  }
3555 
3565  @Deprecated
3566  public static String getModulesOutputDirRelPath() {
3567  return "ModuleOutput"; //NON-NLS
3568  }
3569 
3579  @Deprecated
3580  public static PropertyChangeSupport
3582  return new PropertyChangeSupport(Case.class
3583  );
3584  }
3585 
3594  @Deprecated
3595  public String getModulesOutputDirAbsPath() {
3596  return getModuleDirectory();
3597  }
3598 
3613  @Deprecated
3614  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
3615  try {
3616  Image newDataSource = caseDb.getImageById(imgId);
3617  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
3618  return newDataSource;
3619  } catch (TskCoreException ex) {
3620  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
3621  }
3622  }
3623 
3631  @Deprecated
3632  public Set<TimeZone> getTimeZone() {
3633  return getTimeZones();
3634  }
3635 
3646  @Deprecated
3647  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
3648  deleteReports(reports);
3649  }
3650 
3651 }
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:1574
Case(CaseMetadata caseMetaData)
Definition: Case.java:1794
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:3236
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:608
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1639
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:1989
CoordinationService.Lock caseLock
Definition: Case.java:168
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3039
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:3614
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:2834
static boolean existsCurrentCase()
Definition: Case.java:3552
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:1628
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:1585
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1563
static volatile Frame mainFrame
Definition: Case.java:164
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3513
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:2900
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:1600
static final String CACHE_FOLDER
Definition: Case.java:149
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3160
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1785
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1654
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1068
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1539
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3455
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:3647
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3309
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:2458
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2275
CollaborationMonitor collaborationMonitor
Definition: Case.java:171
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2625
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3566
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2316
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3117
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:1835
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3342
static final String MODULE_FOLDER
Definition: Case.java:154
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2486
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3430
synchronized void openRemoteEventChannel(String channelName)
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2340
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:3197
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:3215
void fireModuleDataEvent(ModuleDataEvent moduleDataEvent)
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2729
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1615
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3581
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:454
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3325
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2412
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:516
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2296
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3137
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2387
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1709
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1526
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1672
static boolean pathExists(String filePath)
Definition: Case.java:3527
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2220
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3471
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:2877
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:1937
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:2515
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1497
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:506
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1550
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:3180
static final String CASE_TEMP_DIR
Definition: Case.java:144
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1511
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:2952
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:3082
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:2366
static synchronized IngestServices getInstance()

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