Autopsy  4.15.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 int CASE_LOCK_TIMEOUT_MINS = 1;
145  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
146  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
147  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
148  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
149  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
150  private static final String LOG_FOLDER = "Log"; //NON-NLS
151  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
152  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
153  private static final String TEMP_FOLDER = "Temp"; //NON-NLS
154  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
155  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
156  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
157  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
158  private static final Logger logger = Logger.getLogger(Case.class.getName());
160  private static final Object caseActionSerializationLock = new Object();
161  private static Future<?> backgroundOpenFileSystemsFuture = null;
162  private static final ExecutorService openFileSystemsExecutor
163  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
164  private static volatile Frame mainFrame;
165  private static volatile Case currentCase;
166  private final CaseMetadata metadata;
167  private volatile ExecutorService caseActionExecutor;
169  private SleuthkitCase caseDb;
171  private CollaborationMonitor collaborationMonitor;
173 
174  /*
175  * Get a reference to the main window of the desktop application to use to
176  * parent pop up dialogs and initialize the application name for use in
177  * changing the main window title.
178  */
179  static {
180  WindowManager.getDefault().invokeWhenUIReady(() -> {
181  mainFrame = WindowManager.getDefault().getMainWindow();
182  });
183  }
184 
188  public enum CaseType {
189 
190  SINGLE_USER_CASE("Single-user case"), //NON-NLS
191  MULTI_USER_CASE("Multi-user case"); //NON-NLS
192 
193  private final String typeName;
194 
202  public static CaseType fromString(String typeName) {
203  if (typeName != null) {
204  for (CaseType c : CaseType.values()) {
205  if (typeName.equalsIgnoreCase(c.toString())) {
206  return c;
207  }
208  }
209  }
210  return null;
211  }
212 
218  @Override
219  public String toString() {
220  return typeName;
221  }
222 
228  @Messages({
229  "Case_caseType_singleUser=Single-user case",
230  "Case_caseType_multiUser=Multi-user case"
231  })
233  if (fromString(typeName) == SINGLE_USER_CASE) {
234  return Bundle.Case_caseType_singleUser();
235  } else {
236  return Bundle.Case_caseType_multiUser();
237  }
238  }
239 
245  private CaseType(String typeName) {
246  this.typeName = typeName;
247  }
248 
259  @Deprecated
260  public boolean equalsName(String otherTypeName) {
261  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
262  }
263 
264  };
265 
270  public enum Events {
271 
279  @Deprecated
288  @Deprecated
297  @Deprecated
408  /*
409  * An item in the central repository has had its comment modified. The
410  * old value is null, the new value is string for current comment.
411  */
413 
414  };
415 
421  private final class SleuthkitEventListener {
422 
423  @Subscribe
424  public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
425  eventPublisher.publish(new TimelineEventAddedEvent(event));
426  }
427 
428  @SuppressWarnings("deprecation")
429  @Subscribe
430  public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) {
431  for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) {
432  /*
433  * IngestServices.fireModuleDataEvent is deprecated to
434  * discourage ingest module writers from using it (they should
435  * use org.sleuthkit.datamodel.Blackboard.postArtifact(s)
436  * instead), but a way to publish
437  * Blackboard.ArtifactsPostedEvents from the SleuthKit layer as
438  * Autopsy ModuleDataEvents is still needed.
439  */
441  event.getModuleName(),
442  artifactType,
443  event.getArtifacts(artifactType)));
444  }
445  }
446  }
447 
454  public static void addPropertyChangeListener(PropertyChangeListener listener) {
455  addEventSubscriber(Stream.of(Events.values())
456  .map(Events::toString)
457  .collect(Collectors.toSet()), listener);
458  }
459 
466  public static void removePropertyChangeListener(PropertyChangeListener listener) {
467  removeEventSubscriber(Stream.of(Events.values())
468  .map(Events::toString)
469  .collect(Collectors.toSet()), listener);
470  }
471 
480  @Deprecated
481  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
482  eventPublisher.addSubscriber(eventNames, subscriber);
483  }
484 
491  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
492  eventTypes.forEach((Events event) -> {
493  eventPublisher.addSubscriber(event.toString(), subscriber);
494  });
495  }
496 
505  @Deprecated
506  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
507  eventPublisher.addSubscriber(eventName, subscriber);
508  }
509 
516  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
517  eventPublisher.removeSubscriber(eventName, subscriber);
518  }
519 
526  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
527  eventPublisher.removeSubscriber(eventNames, subscriber);
528  }
529 
536  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
537  eventTypes.forEach((Events event) -> {
538  eventPublisher.removeSubscriber(event.toString(), subscriber);
539  });
540  }
541 
550  public static boolean isValidName(String caseName) {
551  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
552  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
553  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
554  }
555 
580  @Deprecated
581  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
582  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
583  }
584 
604  @Messages({
605  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
606  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
607  })
608  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
609  if (caseDetails.getCaseDisplayName().isEmpty()) {
610  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
611  }
612  if (caseDir.isEmpty()) {
613  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
614  }
615  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
616  }
617 
631  @Messages({
632  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
633  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
634  })
635  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
637  try {
638  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
639  } catch (CaseMetadataException ex) {
640  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
641  }
643  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
644  }
645  openAsCurrentCase(new Case(metadata), false);
646  }
647 
653  public static boolean isCaseOpen() {
654  return currentCase != null;
655  }
656 
664  public static Case getCurrentCase() {
665  try {
666  return getCurrentCaseThrows();
667  } catch (NoCurrentCaseException ex) {
668  /*
669  * Throw a runtime exception, since this is a programming error.
670  */
671  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
672  }
673  }
674 
690  /*
691  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
692  * case method.
693  */
694  Case openCase = currentCase;
695  if (openCase == null) {
696  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
697  } else {
698  return openCase;
699  }
700  }
701 
710  @Messages({
711  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
712  "Case.progressIndicatorTitle.closingCase=Closing Case"
713  })
714  public static void closeCurrentCase() throws CaseActionException {
715  synchronized (caseActionSerializationLock) {
716  if (null == currentCase) {
717  return;
718  }
719  Case closedCase = currentCase;
720  try {
721  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
722  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
723  currentCase = null;
724  closedCase.doCloseCaseAction();
725  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
726  } catch (CaseActionException ex) {
727  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
728  throw ex;
729  } finally {
732  }
733  }
734  }
735  }
736 
745  public static void deleteCurrentCase() throws CaseActionException {
746  synchronized (caseActionSerializationLock) {
747  if (null == currentCase) {
748  return;
749  }
750  CaseMetadata metadata = currentCase.getMetadata();
752  deleteCase(metadata);
753  }
754  }
755 
766  @Messages({
767  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
768  })
769  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
770  synchronized (caseActionSerializationLock) {
771  if (null == currentCase) {
772  return;
773  }
774 
775  /*
776  * Close the current case to release the shared case lock.
777  */
778  CaseMetadata caseMetadata = currentCase.getMetadata();
780 
781  /*
782  * Re-open the case with an exclusive case lock, delete the data
783  * source, and close the case again, releasing the exclusive case
784  * lock.
785  */
786  Case theCase = new Case(caseMetadata);
787  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
788 
789  /*
790  * Re-open the case with a shared case lock.
791  */
792  openAsCurrentCase(new Case(caseMetadata), false);
793  }
794  }
795 
807  @Messages({
808  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
809  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
810  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
811  })
812  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
813  synchronized (caseActionSerializationLock) {
814  if (null != currentCase) {
815  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
816  }
817  }
818 
819  ProgressIndicator progressIndicator;
821  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
822  } else {
823  progressIndicator = new LoggingProgressIndicator();
824  }
825  progressIndicator.start(Bundle.Case_progressMessage_preparing());
826  try {
827  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
828  deleteSingleUserCase(metadata, progressIndicator);
829  } else {
830  try {
831  deleteMultiUserCase(metadata, progressIndicator);
832  } catch (InterruptedException ex) {
833  /*
834  * Note that task cancellation is not currently supported
835  * for this code path, so this catch block is not expected
836  * to be executed.
837  */
838  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
839  }
840  }
841  } finally {
842  progressIndicator.finish();
843  }
844  }
845 
856  @Messages({
857  "Case.progressIndicatorTitle.creatingCase=Creating Case",
858  "Case.progressIndicatorTitle.openingCase=Opening Case",
859  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
860  })
861  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
862  synchronized (caseActionSerializationLock) {
863  if (null != currentCase) {
864  try {
866  } catch (CaseActionException ex) {
867  /*
868  * Notify the user and continue (the error has already been
869  * logged in closeCurrentCase.
870  */
871  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
872  }
873  }
874  try {
875  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
876  String progressIndicatorTitle;
878  if (isNewCase) {
879  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
880  openCaseAction = newCurrentCase::create;
881  } else {
882  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
883  openCaseAction = newCurrentCase::open;
884  }
885  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
886  currentCase = newCurrentCase;
887  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
889  updateGUIForCaseOpened(newCurrentCase);
890  }
891  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
892  } catch (CaseActionCancelledException ex) {
893  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
894  throw ex;
895  } catch (CaseActionException ex) {
896  logger.log(Level.SEVERE, String.format("Error opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()), ex); //NON-NLS
897  throw ex;
898  }
899  }
900  }
901 
910  private static String displayNameToUniqueName(String caseDisplayName) {
911  /*
912  * Replace all non-ASCII characters.
913  */
914  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
915 
916  /*
917  * Replace all control characters.
918  */
919  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
920 
921  /*
922  * Replace /, \, :, ?, space, ' ".
923  */
924  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
925 
926  /*
927  * Make it all lowercase.
928  */
929  uniqueCaseName = uniqueCaseName.toLowerCase();
930 
931  /*
932  * Add a time stamp for uniqueness.
933  */
934  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
935  Date date = new Date();
936  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
937 
938  return uniqueCaseName;
939  }
940 
950  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
951  /*
952  * Check the case directory path and permissions. The case directory may
953  * already exist.
954  */
955  File caseDir = new File(caseDirPath);
956  if (caseDir.exists()) {
957  if (caseDir.isFile()) {
958  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
959  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
960  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
961  }
962  }
963 
964  /*
965  * Create the case directory, if it does not already exist.
966  */
967  if (!caseDir.mkdirs()) {
968  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
969  }
970 
971  /*
972  * Create the subdirectories of the case directory, if they do not
973  * already exist. Note that multi-user cases get an extra layer of
974  * subdirectories, one subdirectory per application host machine.
975  */
976  String hostPathComponent = "";
977  if (caseType == CaseType.MULTI_USER_CASE) {
978  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
979  }
980 
981  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
982  if (!exportDir.toFile().mkdirs()) {
983  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
984  }
985 
986  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
987  if (!logsDir.toFile().mkdirs()) {
988  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
989  }
990 
991  Path tempDir = Paths.get(caseDirPath, hostPathComponent, TEMP_FOLDER);
992  if (!tempDir.toFile().mkdirs()) {
993  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", tempDir));
994  }
995 
996  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
997  if (!cacheDir.toFile().mkdirs()) {
998  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
999  }
1000 
1001  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
1002  if (!moduleOutputDir.toFile().mkdirs()) {
1003  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
1004  }
1005 
1006  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1007  if (!reportsDir.toFile().mkdirs()) {
1008  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1009  }
1010  }
1011 
1019  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1020  Map<Long, String> imgPaths = new HashMap<>();
1021  try {
1022  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1023  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1024  if (entry.getValue().size() > 0) {
1025  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1026  }
1027  }
1028  } catch (TskCoreException ex) {
1029  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1030  }
1031  return imgPaths;
1032  }
1033 
1044  @Messages({
1045  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1046  })
1047  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1048  try {
1049  Path caseDirPath = Paths.get(caseDir);
1050  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1051  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1052  return lock;
1053  } catch (InterruptedException ex) {
1054  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1055  } catch (CoordinationServiceException ex) {
1056  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1057  }
1058  }
1059 
1060  private static String getNameForTitle() {
1061  //Method should become unnecessary once technical debt story 3334 is done.
1062  if (UserPreferences.getAppName().equals(Version.getName())) {
1063  //Available version number is version number for this application
1064  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1065  } else {
1066  return UserPreferences.getAppName();
1067  }
1068  }
1069 
1073  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1075  SwingUtilities.invokeLater(() -> {
1076  /*
1077  * If the case database was upgraded for a new schema and a
1078  * backup database was created, notify the user.
1079  */
1080  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1081  String backupDbPath = caseDb.getBackupDatabasePath();
1082  if (null != backupDbPath) {
1083  JOptionPane.showMessageDialog(
1084  mainFrame,
1085  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1086  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1087  JOptionPane.INFORMATION_MESSAGE);
1088  }
1089 
1090  /*
1091  * Look for the files for the data sources listed in the case
1092  * database and give the user the opportunity to locate any that
1093  * are missing.
1094  */
1095  Map<Long, String> imgPaths = getImagePaths(caseDb);
1096  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1097  long obj_id = entry.getKey();
1098  String path = entry.getValue();
1099  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1100  if (!fileExists) {
1101  int response = JOptionPane.showConfirmDialog(
1102  mainFrame,
1103  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1104  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1105  JOptionPane.YES_NO_OPTION);
1106  if (response == JOptionPane.YES_OPTION) {
1107  MissingImageDialog.makeDialog(obj_id, caseDb);
1108  } else {
1109  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1110 
1111  }
1112  }
1113  }
1114 
1115  /*
1116  * Enable the case-specific actions.
1117  */
1118  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1119  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1120  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1121  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1122  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1123  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1124  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1125  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1126  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1127  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1128 
1129  /*
1130  * Add the case to the recent cases tracker that supplies a list
1131  * of recent cases to the recent cases menu item and the
1132  * open/create case dialog.
1133  */
1134  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1135 
1136  /*
1137  * Open the top components (windows within the main application
1138  * window).
1139  *
1140  * Note: If the core windows are not opened here, they will be
1141  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1142  * method on a DATA_SOURCE_ADDED event.
1143  */
1144  if (newCurrentCase.hasData()) {
1146  }
1147 
1148  /*
1149  * Reset the main window title to:
1150  *
1151  * [curent case display name] - [application name].
1152  */
1153  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1154  });
1155  }
1156  }
1157 
1158  /*
1159  * Update the GUI to to reflect the lack of a current case.
1160  */
1161  private static void updateGUIForCaseClosed() {
1163  SwingUtilities.invokeLater(() -> {
1164  /*
1165  * Close the top components (windows within the main application
1166  * window).
1167  */
1169 
1170  /*
1171  * Disable the case-specific menu items.
1172  */
1173  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1174  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1175  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1176  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1177  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1178  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1179  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1180  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1181  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1182  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1183 
1184  /*
1185  * Clear the notifications in the notfier component in the lower
1186  * right hand corner of the main application window.
1187  */
1189 
1190  /*
1191  * Reset the main window title to be just the application name,
1192  * instead of [curent case display name] - [application name].
1193  */
1194  mainFrame.setTitle(getNameForTitle());
1195  });
1196  }
1197  }
1198 
1202  private static void clearTempSubDir(String tempSubDirPath) {
1203  File tempFolder = new File(tempSubDirPath);
1204  if (tempFolder.isDirectory()) {
1205  File[] files = tempFolder.listFiles();
1206  if (files.length > 0) {
1207  for (File file : files) {
1208  if (file.isDirectory()) {
1209  FileUtil.deleteDir(file);
1210  } else {
1211  file.delete();
1212  }
1213  }
1214  }
1215  }
1216  }
1217 
1223  public SleuthkitCase getSleuthkitCase() {
1224  return this.caseDb;
1225  }
1226 
1233  return caseServices;
1234  }
1235 
1242  return metadata.getCaseType();
1243  }
1244 
1250  public String getCreatedDate() {
1251  return metadata.getCreatedDate();
1252  }
1253 
1259  public String getName() {
1260  return metadata.getCaseName();
1261  }
1262 
1268  public String getDisplayName() {
1269  return metadata.getCaseDisplayName();
1270  }
1271 
1277  public String getNumber() {
1278  return metadata.getCaseNumber();
1279  }
1280 
1286  public String getExaminer() {
1287  return metadata.getExaminer();
1288  }
1289 
1295  public String getExaminerPhone() {
1296  return metadata.getExaminerPhone();
1297  }
1298 
1304  public String getExaminerEmail() {
1305  return metadata.getExaminerEmail();
1306  }
1307 
1313  public String getCaseNotes() {
1314  return metadata.getCaseNotes();
1315  }
1316 
1322  public String getCaseDirectory() {
1323  return metadata.getCaseDirectory();
1324  }
1325 
1334  public String getOutputDirectory() {
1335  String caseDirectory = getCaseDirectory();
1336  Path hostPath;
1337  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1338  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1339  } else {
1340  hostPath = Paths.get(caseDirectory);
1341  }
1342  if (!hostPath.toFile().exists()) {
1343  hostPath.toFile().mkdirs();
1344  }
1345  return hostPath.toString();
1346  }
1347 
1354  public String getTempDirectory() {
1355  return getOrCreateSubdirectory(TEMP_FOLDER);
1356  }
1357 
1364  public String getCacheDirectory() {
1365  return getOrCreateSubdirectory(CACHE_FOLDER);
1366  }
1367 
1374  public String getExportDirectory() {
1375  return getOrCreateSubdirectory(EXPORT_FOLDER);
1376  }
1377 
1384  public String getLogDirectoryPath() {
1385  return getOrCreateSubdirectory(LOG_FOLDER);
1386  }
1387 
1394  public String getReportDirectory() {
1395  return getOrCreateSubdirectory(REPORTS_FOLDER);
1396  }
1397 
1404  public String getConfigDirectory() {
1405  return getOrCreateSubdirectory(CONFIG_FOLDER);
1406  }
1407 
1414  public String getModuleDirectory() {
1415  return getOrCreateSubdirectory(MODULE_FOLDER);
1416  }
1417 
1426  Path path = Paths.get(getModuleDirectory());
1428  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1429  } else {
1430  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1431  }
1432  }
1433 
1443  public List<Content> getDataSources() throws TskCoreException {
1444  return caseDb.getRootObjects();
1445  }
1446 
1452  public Set<TimeZone> getTimeZones() {
1453  Set<TimeZone> timezones = new HashSet<>();
1454  try {
1455  for (Content c : getDataSources()) {
1456  final Content dataSource = c.getDataSource();
1457  if ((dataSource != null) && (dataSource instanceof Image)) {
1458  Image image = (Image) dataSource;
1459  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1460  }
1461  }
1462  } catch (TskCoreException ex) {
1463  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1464  }
1465  return timezones;
1466  }
1467 
1474  public String getTextIndexName() {
1475  return getMetadata().getTextIndexName();
1476  }
1477 
1484  public boolean hasData() {
1485  boolean hasDataSources = false;
1486  try {
1487  hasDataSources = (getDataSources().size() > 0);
1488  } catch (TskCoreException ex) {
1489  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1490  }
1491  return hasDataSources;
1492  }
1493 
1504  public void notifyAddingDataSource(UUID eventId) {
1505  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1506  }
1507 
1518  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1519  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1520  }
1521 
1533  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1534  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1535  }
1536 
1546  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1547  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1548  }
1549 
1557  public void notifyContentTagAdded(ContentTag newTag) {
1558  notifyContentTagAdded(newTag, null);
1559  }
1560 
1570  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1571  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1572  }
1573 
1581  public void notifyContentTagDeleted(ContentTag deletedTag) {
1582  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1583  }
1584 
1592  public void notifyTagDefinitionChanged(String changedTagName) {
1593  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1594  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1595  }
1596 
1607  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1608  try {
1609  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1610  } catch (NoCurrentCaseException ex) {
1611  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1612  }
1613  }
1614 
1622  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1623  notifyBlackBoardArtifactTagAdded(newTag, null);
1624  }
1625 
1635  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1636  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1637  }
1638 
1646  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1647  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1648  }
1649 
1661  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1662  addReport(localPath, srcModuleName, reportName, null);
1663  }
1664 
1679  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1680  String normalizedLocalPath;
1681  try {
1682  if (localPath.toLowerCase().contains("http:")) {
1683  normalizedLocalPath = localPath;
1684  } else {
1685  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1686  }
1687  } catch (InvalidPathException ex) {
1688  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1689  throw new TskCoreException(errorMsg, ex);
1690  }
1691  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1692  eventPublisher.publish(new ReportAddedEvent(report));
1693  return report;
1694  }
1695 
1704  public List<Report> getAllReports() throws TskCoreException {
1705  return this.caseDb.getAllReports();
1706  }
1707 
1716  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1717  for (Report report : reports) {
1718  this.caseDb.deleteReport(report);
1719  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1720  }
1721  }
1722 
1728  CaseMetadata getMetadata() {
1729  return metadata;
1730  }
1731 
1739  @Messages({
1740  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1741  })
1742  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1743  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1744  try {
1745  metadata.setCaseDetails(caseDetails);
1746  } catch (CaseMetadataException ex) {
1747  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1748  }
1749  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1750  try {
1751  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
1752  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
1753  CaseNodeData.writeCaseNodeData(nodeData);
1754  } catch (CaseNodeDataException | InterruptedException ex) {
1755  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
1756  }
1757  }
1758  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
1759  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
1760  }
1761  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
1762  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
1763  }
1764  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1765  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
1766  }
1767  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
1768  if (RuntimeProperties.runningWithGUI()) {
1769  SwingUtilities.invokeLater(() -> {
1770  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
1771  try {
1772  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
1773  } catch (Exception ex) {
1774  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1775  }
1776  });
1777  }
1778  }
1779 
1792  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
1793  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
1794  }
1795 
1801  private Case(CaseMetadata caseMetaData) {
1802  metadata = caseMetaData;
1803  sleuthkitEventListener = new SleuthkitEventListener();
1804  }
1805 
1835  @Messages({
1836  "Case.progressIndicatorCancelButton.label=Cancel",
1837  "Case.progressMessage.preparing=Preparing...",
1838  "Case.progressMessage.cancelling=Cancelling...",
1839  "Case.exceptionMessage.cancelled=Cancelled.",
1840  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1841  })
1842  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
1843  /*
1844  * Create and start either a GUI progress indicator (with or without a
1845  * cancel button) or a logging progress indicator.
1846  */
1847  CancelButtonListener cancelButtonListener = null;
1848  ProgressIndicator progressIndicator;
1850  if (allowCancellation) {
1851  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
1852  progressIndicator = new ModalDialogProgressIndicator(
1853  mainFrame,
1854  progressIndicatorTitle,
1855  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1856  Bundle.Case_progressIndicatorCancelButton_label(),
1857  cancelButtonListener);
1858  } else {
1859  progressIndicator = new ModalDialogProgressIndicator(
1860  mainFrame,
1861  progressIndicatorTitle);
1862  }
1863  } else {
1864  progressIndicator = new LoggingProgressIndicator();
1865  }
1866  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1867 
1868  /*
1869  * Do the case action in the single thread in the case action executor.
1870  * If the case is a multi-user case, a case lock is acquired and held
1871  * until explictly released and an exclusive case resources lock is
1872  * aquired and held for the duration of the action.
1873  */
1874  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
1875  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
1876  Future<Void> future = caseActionExecutor.submit(() -> {
1877  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1878  caseAction.execute(progressIndicator, additionalParams);
1879  } else {
1880  acquireCaseLock(caseLockType);
1881  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
1882  if (null == resourcesLock) {
1883  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
1884  }
1885  caseAction.execute(progressIndicator, additionalParams);
1886  } catch (CaseActionException ex) {
1887  releaseCaseLock();
1888  throw ex;
1889  }
1890  }
1891  return null;
1892  });
1893  if (null != cancelButtonListener) {
1894  cancelButtonListener.setCaseActionFuture(future);
1895  }
1896 
1897  /*
1898  * Wait for the case action task to finish.
1899  */
1900  try {
1901  future.get();
1902  } catch (InterruptedException discarded) {
1903  /*
1904  * The thread this method is running in has been interrupted.
1905  */
1906  if (null != cancelButtonListener) {
1907  cancelButtonListener.actionPerformed(null);
1908  } else {
1909  future.cancel(true);
1910  }
1911  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1912  } catch (CancellationException discarded) {
1913  /*
1914  * The case action has been cancelled.
1915  */
1916  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1917  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1918  } catch (ExecutionException ex) {
1919  /*
1920  * The case action has thrown an exception.
1921  */
1922  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
1923  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1924  } finally {
1925  progressIndicator.finish();
1926  }
1927  }
1928 
1944  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
1945  assert (additionalParams == null);
1946  try {
1948  createCaseDirectoryIfDoesNotExist(progressIndicator);
1950  switchLoggingToCaseLogsDirectory(progressIndicator);
1952  saveCaseMetadataToFile(progressIndicator);
1954  createCaseNodeData(progressIndicator);
1957  createCaseDatabase(progressIndicator);
1959  openCaseLevelServices(progressIndicator);
1961  openAppServiceCaseResources(progressIndicator, true);
1963  openCommunicationChannels(progressIndicator);
1964  return null;
1965 
1966  } catch (CaseActionException ex) {
1967  /*
1968  * Cancellation or failure. The sleep is a little hack to clear the
1969  * interrupted flag for this thread if this is a cancellation
1970  * scenario, so that the clean up can run to completion in the
1971  * current thread.
1972  */
1973  try {
1974  Thread.sleep(1);
1975  } catch (InterruptedException discarded) {
1976  }
1977  close(progressIndicator);
1978  throw ex;
1979  }
1980  }
1981 
1996  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
1997  assert (additionalParams == null);
1998  try {
2000  switchLoggingToCaseLogsDirectory(progressIndicator);
2002  updateCaseNodeData(progressIndicator);
2004  deleteTempfilesFromCaseDirectory(progressIndicator);
2006  openCaseDataBase(progressIndicator);
2008  openCaseLevelServices(progressIndicator);
2010  openAppServiceCaseResources(progressIndicator, false);
2012  openCommunicationChannels(progressIndicator);
2015  return null;
2016 
2017  } catch (CaseActionException ex) {
2018  /*
2019  * Cancellation or failure. The sleep is a little hack to clear the
2020  * interrupted flag for this thread if this is a cancellation
2021  * scenario, so that the clean up can run to completion in the
2022  * current thread.
2023  */
2024  try {
2025  Thread.sleep(1);
2026  } catch (InterruptedException discarded) {
2027  }
2028  close(progressIndicator);
2029  throw ex;
2030  }
2031  }
2032 
2042  @Messages({
2043  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2044  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2045  })
2047  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2048  backgroundOpenFileSystemsFuture.cancel(true);
2049  }
2050 
2052  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2053  }
2054 
2059  private static class BackgroundOpenFileSystemsTask implements Runnable {
2060 
2061  private final SleuthkitCase tskCase;
2062  private final String caseName;
2063  private final long MAX_IMAGE_THRESHOLD = 100;
2065 
2074  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2075  this.tskCase = tskCase;
2076  this.progressIndicator = progressIndicator;
2077  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2078  }
2079 
2087  private void checkIfCancelled() throws InterruptedException {
2088  if (Thread.interrupted()) {
2089  throw new InterruptedException();
2090  }
2091  }
2092 
2098  private List<Image> getImages() {
2099  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2100  try {
2101  return this.tskCase.getImages();
2102  } catch (TskCoreException ex) {
2103  logger.log(
2104  Level.SEVERE,
2105  String.format("Could not obtain images while opening case: %s.", caseName),
2106  ex);
2107 
2108  return null;
2109  }
2110  }
2111 
2121  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2122  byte[] tempBuff = new byte[512];
2123 
2124  for (Image image : images) {
2125  String imageStr = image.getName();
2126 
2127  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2128 
2129  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2130  checkIfCancelled();
2131  for (FileSystem fileSystem : fileSystems) {
2132  fileSystem.read(tempBuff, 0, 512);
2133  checkIfCancelled();
2134  }
2135 
2136  }
2137  }
2138 
2139  @Override
2140  public void run() {
2141  try {
2142  checkIfCancelled();
2143  List<Image> images = getImages();
2144  if (images == null) {
2145  return;
2146  }
2147 
2148  if (images.size() > MAX_IMAGE_THRESHOLD) {
2149  // If we have a large number of images, don't try to preload anything
2150  logger.log(
2151  Level.INFO,
2152  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2153  return;
2154  }
2155 
2156  checkIfCancelled();
2157  openFileSystems(images);
2158  } catch (InterruptedException ex) {
2159  logger.log(
2160  Level.INFO,
2161  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2162  } catch (Exception ex) {
2163  // Exception firewall
2164  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2165  }
2166  }
2167 
2168  }
2169 
2184  @Messages({
2185  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2186  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2187  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2188  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2189  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2190  assert (additionalParams instanceof Long);
2191  open(progressIndicator, null);
2192  try {
2193  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2194  Long dataSourceObjectID = (Long) additionalParams;
2195  try {
2196  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2197  if (dataSource == null) {
2198  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2199  }
2200  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2201  } catch (TskDataException | TskCoreException ex) {
2202  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2203  }
2204  try {
2205  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2206  } catch (KeywordSearchServiceException ex) {
2207  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2208  }
2209  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2210  return null;
2211  } finally {
2212  close(progressIndicator);
2213  releaseCaseLock();
2214  }
2215  }
2216 
2227  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2228 
2229  if (portableCaseFolder.exists()) {
2230  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2231  }
2232  if (!portableCaseFolder.mkdirs()) {
2233  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2234  }
2235 
2236  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2238  try {
2239  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2240  caseName, details, metadata);
2241  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2242  } catch (CaseMetadataException ex) {
2243  throw new TskCoreException("Error creating case metadata", ex);
2244  }
2245 
2246  // Create the Sleuthkit case
2247  SleuthkitCase portableSleuthkitCase;
2248  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2249  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2250 
2251  return portableSleuthkitCase;
2252  }
2253 
2264  if (Thread.currentThread().isInterrupted()) {
2265  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2266  }
2267  }
2268 
2279  @Messages({
2280  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2281  })
2282  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2283  /*
2284  * TODO (JIRA-2180): Always create the case directory as part of the
2285  * case creation process.
2286  */
2287  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2288  if (new File(metadata.getCaseDirectory()).exists() == false) {
2289  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2290  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2291  }
2292  }
2293 
2300  @Messages({
2301  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2302  })
2303  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2304  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2306  }
2307 
2319  @Messages({
2320  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2321  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2322  })
2323  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2324  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2325  try {
2326  this.metadata.writeToFile();
2327  } catch (CaseMetadataException ex) {
2328  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2329  }
2330  }
2331 
2343  @Messages({
2344  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2345  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2346  })
2347  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2349  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2350  try {
2351  CaseNodeData.createCaseNodeData(metadata);
2352  } catch (CaseNodeDataException | InterruptedException ex) {
2353  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2354  }
2355  }
2356  }
2357 
2369  @Messages({
2370  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2371  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2372  })
2373  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2375  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2376  try {
2378  nodeData.setLastAccessDate(new Date());
2379  CaseNodeData.writeCaseNodeData(nodeData);
2380  } catch (CaseNodeDataException | InterruptedException ex) {
2381  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2382  }
2383  }
2384  }
2385 
2391  @Messages({
2392  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2393  })
2394  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2395  /*
2396  * Clear the temp subdirectory of the case directory.
2397  */
2398  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2400  }
2401 
2413  @Messages({
2414  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2415  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2416  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2417  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2418  })
2419  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2420  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2421  try {
2422  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2423  /*
2424  * For single-user cases, the case database is a SQLite database
2425  * with a standard name, physically located in the case
2426  * directory.
2427  */
2428  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2429  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2430  } else {
2431  /*
2432  * For multi-user cases, the case database is a PostgreSQL
2433  * database with a name derived from the case display name,
2434  * physically located on the PostgreSQL database server.
2435  */
2436  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2437  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2438  }
2439  } catch (TskCoreException ex) {
2440  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2441  } catch (UserPreferencesException ex) {
2442  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2443  } catch (CaseMetadataException ex) {
2444  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2445  }
2446  }
2447 
2459  @Messages({
2460  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2461  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2462  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2463  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2464  })
2465  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2466  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2467  try {
2468  String databaseName = metadata.getCaseDatabaseName();
2469  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2470  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
2472  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2473  } else {
2474  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2475  }
2476  } catch (TskUnsupportedSchemaVersionException ex) {
2477  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2478  } catch (UserPreferencesException ex) {
2479  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2480  } catch (TskCoreException ex) {
2481  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2482  }
2483  }
2484 
2491  @Messages({
2492  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2493  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2494  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2495  this.caseServices = new Services(caseDb);
2496  /*
2497  * RC Note: JM put this initialization here. I'm not sure why. However,
2498  * my attempt to put it in the openCaseDatabase method seems to lead to
2499  * intermittent unchecked exceptions concerning a missing subscriber.
2500  */
2501  caseDb.registerForEvents(sleuthkitEventListener);
2502  }
2503 
2516  @NbBundle.Messages({
2517  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2518  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2519  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2520  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2521  })
2522  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2523  /*
2524  * Each service gets its own independently cancellable/interruptible
2525  * task, running in a named thread managed by an executor service, with
2526  * its own progress indicator. This allows for cancellation of the
2527  * opening of case resources for individual services. It also makes it
2528  * possible to ensure that each service task completes before the next
2529  * one starts by awaiting termination of the executor service.
2530  */
2531  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2532 
2533  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2534  )) {
2535  /*
2536  * Create a progress indicator for the task and start the task. If
2537  * running with a GUI, the progress indicator will be a dialog box
2538  * with a Cancel button.
2539  */
2540  CancelButtonListener cancelButtonListener = null;
2541  ProgressIndicator appServiceProgressIndicator;
2543  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2544  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2545  mainFrame,
2546  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2547  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2548  Bundle.Case_progressIndicatorCancelButton_label(),
2549  cancelButtonListener);
2550  } else {
2551  appServiceProgressIndicator = new LoggingProgressIndicator();
2552  }
2553  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2554  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2555  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2556  threadNameSuffix = threadNameSuffix.toLowerCase();
2557  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2558  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2559  Future<Void> future = executor.submit(() -> {
2560  service.openCaseResources(context);
2561  return null;
2562  });
2563  if (null != cancelButtonListener) {
2564  cancelButtonListener.setCaseContext(context);
2565  cancelButtonListener.setCaseActionFuture(future);
2566  }
2567 
2568  /*
2569  * Wait for the task to either be completed or
2570  * cancelled/interrupted, or for the opening of the case to be
2571  * cancelled.
2572  */
2573  try {
2574  future.get();
2575  } catch (InterruptedException discarded) {
2576  /*
2577  * The parent create/open case task has been cancelled.
2578  */
2579  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()));
2580  future.cancel(true);
2581  } catch (CancellationException discarded) {
2582  /*
2583  * The opening of case resources by the application service has
2584  * been cancelled, so the executor service has thrown. Note that
2585  * there is no guarantee the task itself has responded to the
2586  * cancellation request yet.
2587  */
2588  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()));
2589  } catch (ExecutionException ex) {
2590  /*
2591  * An exception was thrown while executing the task. The
2592  * case-specific application service resources are not
2593  * essential. Log an error and notify the user if running the
2594  * desktop GUI, but do not throw.
2595  */
2596  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2598  SwingUtilities.invokeLater(() -> {
2599  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2600  });
2601  }
2602  } finally {
2603  /*
2604  * Shut down the executor service and wait for it to finish.
2605  * This ensures that the task has finished. Without this, it
2606  * would be possible to start the next task before the current
2607  * task responded to a cancellation request.
2608  */
2610  appServiceProgressIndicator.finish();
2611  }
2613  }
2614  }
2615 
2627  @Messages({
2628  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2629  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2630  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2631  })
2632  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2633  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2634  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2635  try {
2636  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2638  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2639  } catch (AutopsyEventException ex) {
2640  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2641  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2642  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2643  }
2644  }
2645  }
2646 
2661  private void doCloseCaseAction() throws CaseActionException {
2662  /*
2663  * Set up either a GUI progress indicator without a Cancel button or a
2664  * logging progress indicator.
2665  */
2666  ProgressIndicator progressIndicator;
2668  progressIndicator = new ModalDialogProgressIndicator(
2669  mainFrame,
2670  Bundle.Case_progressIndicatorTitle_closingCase());
2671  } else {
2672  progressIndicator = new LoggingProgressIndicator();
2673  }
2674  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2675 
2676  /*
2677  * Closing a case is always done in the same non-UI thread that
2678  * opened/created the case. If the case is a multi-user case, this
2679  * ensures that case lock that is held as long as the case is open is
2680  * released in the same thread in which it was acquired, as is required
2681  * by the coordination service.
2682  */
2683  Future<Void> future = caseActionExecutor.submit(() -> {
2684  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2685  close(progressIndicator);
2686  } else {
2687  /*
2688  * Acquire an exclusive case resources lock to ensure only one
2689  * node at a time can create/open/upgrade/close the case
2690  * resources.
2691  */
2692  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2693  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2694  if (null == resourcesLock) {
2695  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2696  }
2697  close(progressIndicator);
2698  } finally {
2699  /*
2700  * Always release the case directory lock that was acquired
2701  * when the case was opened.
2702  */
2703  releaseCaseLock();
2704  }
2705  }
2706  return null;
2707  });
2708 
2709  try {
2710  future.get();
2711  } catch (InterruptedException | CancellationException unused) {
2712  /*
2713  * The wait has been interrupted by interrupting the thread running
2714  * this method. Not allowing cancellation of case closing, so ignore
2715  * the interrupt. Likewise, cancellation of the case closing task is
2716  * not supported.
2717  */
2718  } catch (ExecutionException ex) {
2719  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2720  } finally {
2721  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2722  progressIndicator.finish();
2723  }
2724  }
2725 
2731  @Messages({
2732  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2733  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2734  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2735  })
2736  private void close(ProgressIndicator progressIndicator) {
2738 
2739  /*
2740  * Stop sending/receiving case events to and from other nodes if this is
2741  * a multi-user case.
2742  */
2743  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2744  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2745  if (null != collaborationMonitor) {
2746  collaborationMonitor.shutdown();
2747  }
2748  eventPublisher.closeRemoteEventChannel();
2749  }
2750 
2751  /*
2752  * Allow all registered application services providers to close
2753  * resources related to the case.
2754  */
2755  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2757 
2758  /*
2759  * Close the case database.
2760  */
2761  if (null != caseDb) {
2762  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
2763  caseDb.unregisterForEvents(sleuthkitEventListener);
2764  caseDb.close();
2765  }
2766 
2767  /*
2768  * Switch the log directory.
2769  */
2770  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2772  }
2773 
2778  @Messages({
2779  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2780  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2781  })
2783  /*
2784  * Each service gets its own independently cancellable task, and thus
2785  * its own task progress indicator.
2786  */
2787  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2788  )) {
2789  ProgressIndicator progressIndicator;
2791  progressIndicator = new ModalDialogProgressIndicator(
2792  mainFrame,
2793  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2794  } else {
2795  progressIndicator = new LoggingProgressIndicator();
2796  }
2797  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2798  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
2799  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2800  threadNameSuffix = threadNameSuffix.toLowerCase();
2801  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2802  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2803  Future<Void> future = executor.submit(() -> {
2804  service.closeCaseResources(context);
2805  return null;
2806  });
2807  try {
2808  future.get();
2809  } catch (InterruptedException ex) {
2810  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2811  } catch (CancellationException ex) {
2812  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2813  } catch (ExecutionException ex) {
2814  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
2816  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
2817  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2818  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2819  }
2820  } finally {
2822  progressIndicator.finish();
2823  }
2824  }
2825  }
2826 
2832  @Messages({
2833  "Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.",
2834  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case."
2835  })
2836  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
2837  String caseDir = metadata.getCaseDirectory();
2838  try {
2839  CoordinationService coordinationService = CoordinationService.getInstance();
2840  caseLock = lockType == CaseLockType.SHARED
2841  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
2842  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
2843  if (caseLock == null) {
2844  if (lockType == CaseLockType.SHARED) {
2845  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
2846  } else {
2847  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
2848  }
2849  }
2850  } catch (InterruptedException | CoordinationServiceException ex) {
2851  if (lockType == CaseLockType.SHARED) {
2852  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
2853  } else {
2854  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
2855  }
2856  }
2857  }
2858 
2862  private void releaseCaseLock() {
2863  if (caseLock != null) {
2864  try {
2865  caseLock.release();
2866  caseLock = null;
2868  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
2869  }
2870  }
2871  }
2872 
2879  private String getOrCreateSubdirectory(String subDirectoryName) {
2880  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
2881  if (!subDirectory.exists()) {
2882  subDirectory.mkdirs();
2883  }
2884  return subDirectory.toString();
2885 
2886  }
2887 
2899  @Messages({
2900  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
2901  })
2902  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
2903  boolean errorsOccurred = false;
2904  try {
2905  deleteTextIndex(metadata, progressIndicator);
2906  } catch (KeywordSearchServiceException ex) {
2907  errorsOccurred = true;
2908  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
2909  }
2910 
2911  try {
2912  deleteCaseDirectory(metadata, progressIndicator);
2913  } catch (CaseActionException ex) {
2914  errorsOccurred = true;
2915  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
2916  }
2917 
2918  deleteFromRecentCases(metadata, progressIndicator);
2919 
2920  if (errorsOccurred) {
2921  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
2922  }
2923  }
2924 
2944  @Messages({
2945  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
2946  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
2947  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
2948  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
2949  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
2950  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
2951  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
2952  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
2953  })
2954  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
2955  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
2956  CoordinationService coordinationService;
2957  try {
2958  coordinationService = CoordinationService.getInstance();
2959  } catch (CoordinationServiceException ex) {
2960  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
2961  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
2962  }
2963 
2964  CaseNodeData caseNodeData;
2965  boolean errorsOccurred = false;
2966  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
2967  if (dirLock == null) {
2968  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
2969  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
2970  }
2971 
2972  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
2973  try {
2974  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2975  } catch (CaseNodeDataException | InterruptedException ex) {
2976  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
2977  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
2978  }
2979 
2980  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
2981 
2982  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
2983  try {
2984  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
2985  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
2986  } catch (CoordinationServiceException ex) {
2987  if (!isNoNodeException(ex)) {
2988  errorsOccurred = true;
2989  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
2990  }
2991  } catch (InterruptedException ex) {
2992  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
2993  }
2994 
2995  } catch (CoordinationServiceException ex) {
2996  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
2997  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
2998  }
2999 
3000  if (!errorsOccurred) {
3001  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3002  try {
3003  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3004  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3005  } catch (CoordinationServiceException | InterruptedException ex) {
3006  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
3007  errorsOccurred = true;
3008  }
3009  }
3010 
3011  if (errorsOccurred) {
3012  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3013  }
3014  }
3015 
3040  @Beta
3041  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3042  boolean errorsOccurred = false;
3043  try {
3044  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3045  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3046  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3047  deleteFromRecentCases(metadata, progressIndicator);
3048  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3049  errorsOccurred = true;
3050  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
3051  } catch (KeywordSearchServiceException ex) {
3052  errorsOccurred = true;
3053  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
3054  } catch (CaseActionException ex) {
3055  errorsOccurred = true;
3056  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
3057  }
3058  return errorsOccurred;
3059  }
3060 
3081  @Messages({
3082  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3083  })
3084  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3085  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3086  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3087  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3088  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3089  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3090  Class.forName("org.postgresql.Driver"); //NON-NLS
3091  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3092  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3093  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3094  if (queryResult.next()) {
3095  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3096  statement.execute(deleteCommand);
3097  }
3098  }
3099  }
3101  }
3102  }
3103 
3119  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3121  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3122  deleteTextIndex(metadata, progressIndicator);
3124  }
3125  }
3126 
3136  @Messages({
3137  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3138  })
3139  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3140  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3141 
3142  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3143  )) {
3144  searchService.deleteTextIndex(metadata);
3145  }
3146  }
3147 
3162  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3163  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3164  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3165  deleteCaseDirectory(metadata, progressIndicator);
3167  }
3168  }
3169 
3179  @Messages({
3180  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3181  })
3182  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3183  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3184  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3185  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3186  }
3187  }
3188 
3196  @Messages({
3197  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3198  })
3199  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3201  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3202  SwingUtilities.invokeLater(() -> {
3203  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3204  });
3205  }
3206  }
3207 
3218  boolean isNodeNodeEx = false;
3219  Throwable cause = ex.getCause();
3220  if (cause != null) {
3221  String causeMessage = cause.getMessage();
3222  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3223  }
3224  return isNodeNodeEx;
3225  }
3226 
3238  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3239  try {
3240  caseNodeData.setDeletedFlag(flag);
3241  CaseNodeData.writeCaseNodeData(caseNodeData);
3242  } catch (CaseNodeDataException ex) {
3243  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);
3244 
3245  }
3246  }
3247 
3256  private interface CaseAction<T, V, R> {
3257 
3268  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3269  }
3270 
3275  private enum CaseLockType {
3276  SHARED, EXCLUSIVE;
3277  }
3278 
3283  @ThreadSafe
3284  private final static class CancelButtonListener implements ActionListener {
3285 
3286  private final String cancellationMessage;
3287  @GuardedBy("this")
3288  private boolean cancelRequested;
3289  @GuardedBy("this")
3291  @GuardedBy("this")
3292  private Future<?> caseActionFuture;
3293 
3302  private CancelButtonListener(String cancellationMessage) {
3303  this.cancellationMessage = cancellationMessage;
3304  }
3305 
3311  private synchronized void setCaseContext(CaseContext caseContext) {
3312  this.caseContext = caseContext;
3313  /*
3314  * If the cancel button has already been pressed, pass the
3315  * cancellation on to the case context.
3316  */
3317  if (cancelRequested) {
3318  cancel();
3319  }
3320  }
3321 
3327  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3328  this.caseActionFuture = caseActionFuture;
3329  /*
3330  * If the cancel button has already been pressed, cancel the Future
3331  * of the task.
3332  */
3333  if (cancelRequested) {
3334  cancel();
3335  }
3336  }
3337 
3343  @Override
3344  public synchronized void actionPerformed(ActionEvent event) {
3345  cancel();
3346  }
3347 
3351  private void cancel() {
3352  /*
3353  * At a minimum, set the cancellation requested flag of this
3354  * listener.
3355  */
3356  this.cancelRequested = true;
3357  if (null != this.caseContext) {
3358  /*
3359  * Set the cancellation request flag and display the
3360  * cancellation message in the progress indicator for the case
3361  * context associated with this listener.
3362  */
3364  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3365  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3366  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3367  }
3368  }
3369  this.caseContext.requestCancel();
3370  }
3371  if (null != this.caseActionFuture) {
3372  /*
3373  * Cancel the Future of the task associated with this listener.
3374  * Note that the task thread will be interrupted if the task is
3375  * blocked.
3376  */
3377  this.caseActionFuture.cancel(true);
3378  }
3379  }
3380  }
3381 
3385  private static class TaskThreadFactory implements ThreadFactory {
3386 
3387  private final String threadName;
3388 
3389  private TaskThreadFactory(String threadName) {
3390  this.threadName = threadName;
3391  }
3392 
3393  @Override
3394  public Thread newThread(Runnable task) {
3395  return new Thread(task, threadName);
3396  }
3397 
3398  }
3399 
3407  @Deprecated
3408  public static String getAppName() {
3409  return UserPreferences.getAppName();
3410  }
3411 
3431  @Deprecated
3432  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3433  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3434  }
3435 
3456  @Deprecated
3457  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3458  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3459  }
3460 
3472  @Deprecated
3473  public static void open(String caseMetadataFilePath) throws CaseActionException {
3474  openAsCurrentCase(caseMetadataFilePath);
3475  }
3476 
3486  @Deprecated
3487  public void closeCase() throws CaseActionException {
3488  closeCurrentCase();
3489  }
3490 
3496  @Deprecated
3497  public static void invokeStartupDialog() {
3499  }
3500 
3514  @Deprecated
3515  public static String convertTimeZone(String timeZoneId) {
3516  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3517  }
3518 
3528  @Deprecated
3529  public static boolean pathExists(String filePath) {
3530  return new File(filePath).isFile();
3531  }
3532 
3541  @Deprecated
3542  public static String getAutopsyVersion() {
3543  return Version.getVersion();
3544  }
3545 
3553  @Deprecated
3554  public static boolean existsCurrentCase() {
3555  return isCaseOpen();
3556  }
3557 
3567  @Deprecated
3568  public static String getModulesOutputDirRelPath() {
3569  return "ModuleOutput"; //NON-NLS
3570  }
3571 
3581  @Deprecated
3582  public static PropertyChangeSupport
3584  return new PropertyChangeSupport(Case.class
3585  );
3586  }
3587 
3596  @Deprecated
3597  public String getModulesOutputDirAbsPath() {
3598  return getModuleDirectory();
3599  }
3600 
3615  @Deprecated
3616  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
3617  try {
3618  Image newDataSource = caseDb.getImageById(imgId);
3619  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
3620  return newDataSource;
3621  } catch (TskCoreException ex) {
3622  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
3623  }
3624  }
3625 
3633  @Deprecated
3634  public Set<TimeZone> getTimeZone() {
3635  return getTimeZones();
3636  }
3637 
3648  @Deprecated
3649  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
3650  deleteReports(reports);
3651  }
3652 
3653 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:159
List< Content > getDataSources()
Definition: Case.java:1443
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1581
Case(CaseMetadata caseMetaData)
Definition: Case.java:1801
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:3238
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:608
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1646
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:1996
CoordinationService.Lock caseLock
Definition: Case.java:168
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3041
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:3616
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:2836
static boolean existsCurrentCase()
Definition: Case.java:3554
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:1635
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:161
static final String EXPORT_FOLDER
Definition: Case.java:149
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:950
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1592
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1570
static volatile Frame mainFrame
Definition: Case.java:164
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3515
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:2902
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:1607
static final String CACHE_FOLDER
Definition: Case.java:148
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3162
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:1792
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1661
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1073
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1546
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3457
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:144
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:146
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:812
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:3649
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3311
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:145
static void clearTempSubDir(String tempSubDirPath)
Definition: Case.java:1202
static boolean isValidName(String caseName)
Definition: Case.java:550
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2465
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2282
CollaborationMonitor collaborationMonitor
Definition: Case.java:171
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2632
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3568
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2323
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3119
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:1842
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3344
static final String MODULE_FOLDER
Definition: Case.java:154
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2493
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3432
synchronized void openRemoteEventChannel(String channelName)
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2347
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:3199
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:3217
void fireModuleDataEvent(ModuleDataEvent moduleDataEvent)
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2736
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1622
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3583
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:454
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3327
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2419
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:516
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2303
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3139
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2394
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1716
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1533
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1679
static boolean pathExists(String filePath)
Definition: Case.java:3529
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2227
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3473
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:861
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1047
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:2879
boolean equalsName(String otherTypeName)
Definition: Case.java:260
static final String EVENT_CHANNEL_NAME
Definition: Case.java:147
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:150
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:1944
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:2522
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1504
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:506
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1557
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:3182
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1518
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:2954
static final String CONFIG_FOLDER
Definition: Case.java:152
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:3084
static final String REPORTS_FOLDER
Definition: Case.java:151
static final String TEMP_FOLDER
Definition: Case.java:153
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:481
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2373
static synchronized IngestServices getInstance()

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