Autopsy  4.19.3
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-2021 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;
25 import java.awt.Cursor;
27 import java.awt.Frame;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.beans.PropertyChangeListener;
31 import java.beans.PropertyChangeSupport;
32 import java.io.File;
33 import java.lang.reflect.InvocationTargetException;
34 import java.nio.file.InvalidPathException;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.sql.Connection;
38 import java.sql.DriverManager;
39 import java.sql.ResultSet;
40 import java.sql.SQLException;
41 import java.sql.Statement;
42 import java.text.SimpleDateFormat;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.TimeZone;
52 import java.util.UUID;
53 import java.util.concurrent.CancellationException;
54 import java.util.concurrent.ExecutionException;
55 import java.util.concurrent.ExecutorService;
56 import java.util.concurrent.Executors;
57 import java.util.concurrent.Future;
58 import java.util.concurrent.ThreadFactory;
59 import java.util.concurrent.TimeUnit;
60 import java.util.logging.Level;
61 import java.util.stream.Collectors;
62 import java.util.stream.Stream;
63 import javax.annotation.concurrent.GuardedBy;
64 import javax.annotation.concurrent.ThreadSafe;
65 import javax.swing.JOptionPane;
66 import javax.swing.SwingUtilities;
67 import org.apache.commons.lang3.StringUtils;
68 import org.openide.util.Lookup;
69 import org.openide.util.NbBundle;
70 import org.openide.util.NbBundle.Messages;
71 import org.openide.util.actions.CallableSystemAction;
72 import org.openide.windows.WindowManager;
146 import org.sleuthkit.datamodel.Blackboard;
147 import org.sleuthkit.datamodel.BlackboardArtifact;
148 import org.sleuthkit.datamodel.BlackboardArtifactTag;
149 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
150 import org.sleuthkit.datamodel.Content;
151 import org.sleuthkit.datamodel.ContentTag;
152 import org.sleuthkit.datamodel.DataSource;
153 import org.sleuthkit.datamodel.FileSystem;
154 import org.sleuthkit.datamodel.Host;
155 import org.sleuthkit.datamodel.Image;
156 import org.sleuthkit.datamodel.OsAccount;
157 import org.sleuthkit.datamodel.Person;
158 import org.sleuthkit.datamodel.Report;
159 import org.sleuthkit.datamodel.SleuthkitCase;
160 import org.sleuthkit.datamodel.TimelineManager;
161 import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
162 import org.sleuthkit.datamodel.TskCoreException;
163 import org.sleuthkit.datamodel.TskDataException;
164 import org.sleuthkit.datamodel.TskEvent;
165 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
166 
170 public class Case {
171 
172  private static final int CASE_LOCK_TIMEOUT_MINS = 1;
173  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
174  private static final String APP_NAME = UserPreferences.getAppName();
175  private static final String TEMP_FOLDER = "Temp";
176  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
177  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
178  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
179  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
180  private static final String LOG_FOLDER = "Log"; //NON-NLS
181  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
182  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
183  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
184  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
185  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
186  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
187  private static final Logger logger = Logger.getLogger(Case.class.getName());
189  private static final Object caseActionSerializationLock = new Object();
190  private static Future<?> backgroundOpenFileSystemsFuture = null;
191  private static final ExecutorService openFileSystemsExecutor
192  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
193  private static volatile Frame mainFrame;
194  private static volatile Case currentCase;
195  private final CaseMetadata metadata;
196  private volatile ExecutorService caseActionExecutor;
198  private SleuthkitCase caseDb;
200  private CollaborationMonitor collaborationMonitor;
202 
203  private volatile boolean hasDataSource = false;
204  private volatile boolean hasData = false;
205 
206  /*
207  * Get a reference to the main window of the desktop application to use to
208  * parent pop up dialogs and initialize the application name for use in
209  * changing the main window title.
210  */
211  static {
212  WindowManager.getDefault().invokeWhenUIReady(() -> {
213  mainFrame = WindowManager.getDefault().getMainWindow();
214  });
215  }
216 
220  public enum CaseType {
221 
222  SINGLE_USER_CASE("Single-user case"), //NON-NLS
223  MULTI_USER_CASE("Multi-user case"); //NON-NLS
224 
225  private final String typeName;
226 
234  public static CaseType fromString(String typeName) {
235  if (typeName != null) {
236  for (CaseType c : CaseType.values()) {
237  if (typeName.equalsIgnoreCase(c.toString())) {
238  return c;
239  }
240  }
241  }
242  return null;
243  }
244 
250  @Override
251  public String toString() {
252  return typeName;
253  }
254 
260  @Messages({
261  "Case_caseType_singleUser=Single-user case",
262  "Case_caseType_multiUser=Multi-user case"
263  })
265  if (fromString(typeName) == SINGLE_USER_CASE) {
266  return Bundle.Case_caseType_singleUser();
267  } else {
268  return Bundle.Case_caseType_multiUser();
269  }
270  }
271 
277  private CaseType(String typeName) {
278  this.typeName = typeName;
279  }
280 
291  @Deprecated
292  public boolean equalsName(String otherTypeName) {
293  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
294  }
295 
296  };
297 
302  public enum Events {
303 
311  @Deprecated
320  @Deprecated
329  @Deprecated
440  /*
441  * An item in the central repository has had its comment modified. The
442  * old value is null, the new value is string for current comment.
443  */
493 
498 
503 
508 
513 
518 
519  };
520 
526  private final class SleuthkitEventListener {
527 
528  @Subscribe
529  public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
530  eventPublisher.publish(new TimelineEventAddedEvent(event));
531  }
532 
533  @Subscribe
534  public void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event) {
535  hasData = true;
536  eventPublisher.publish(new OsAccountsAddedEvent(event.getOsAcounts()));
537  }
538 
539  @Subscribe
540  public void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event) {
541  eventPublisher.publish(new OsAccountsUpdatedEvent(event.getOsAcounts()));
542  }
543 
544  @Subscribe
545  public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) {
546  try {
547  hasData = dbHasData();
548  } catch (TskCoreException ex) {
549  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
550  }
551  eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds()));
552  }
553 
554  @Subscribe
555  public void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event) {
556  eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances()));
557  }
558 
565  @Subscribe
566  public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) {
567  hasData = true;
568  eventPublisher.publish(new HostsAddedEvent(event.getHosts()));
569  }
570 
577  @Subscribe
578  public void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event) {
579  eventPublisher.publish(new HostsUpdatedEvent(event.getHosts()));
580  }
581 
588  @Subscribe
589  public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) {
590  try {
591  hasData = dbHasData();
592  } catch (TskCoreException ex) {
593  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
594  }
595 
596  eventPublisher.publish(new HostsDeletedEvent(event.getHostIds()));
597  }
598 
605  @Subscribe
606  public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) {
607  eventPublisher.publish(new PersonsAddedEvent(event.getPersons()));
608  }
609 
616  @Subscribe
617  public void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event) {
618  eventPublisher.publish(new PersonsUpdatedEvent(event.getPersons()));
619  }
620 
627  @Subscribe
628  public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) {
629  eventPublisher.publish(new PersonsDeletedEvent(event.getPersonIds()));
630  }
631 
632  @Subscribe
633  public void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event) {
634  eventPublisher.publish(new HostsAddedToPersonEvent(event.getPerson(), event.getHosts()));
635  }
636 
637  @Subscribe
638  public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) {
639  eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds()));
640  }
641 
642  @Subscribe
643  public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) {
644  eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames()));
645  }
646 
647  @Subscribe
648  public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) {
649  eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames()));
650  }
651 
652  @Subscribe
653  public void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event) {
654  eventPublisher.publish(new TagNamesDeletedEvent(event.getTagNameIds()));
655  }
656 
657  @Subscribe
658  public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) {
659  eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets()));
660  }
661 
662  @Subscribe
663  public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) {
664  eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds()));
665  }
666  }
667 
674  public static void addPropertyChangeListener(PropertyChangeListener listener) {
675  addEventSubscriber(Stream.of(Events.values())
676  .map(Events::toString)
677  .collect(Collectors.toSet()), listener);
678  }
679 
686  public static void removePropertyChangeListener(PropertyChangeListener listener) {
687  removeEventSubscriber(Stream.of(Events.values())
688  .map(Events::toString)
689  .collect(Collectors.toSet()), listener);
690  }
691 
700  @Deprecated
701  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
702  eventPublisher.addSubscriber(eventNames, subscriber);
703  }
704 
711  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
712  eventTypes.forEach((Events event) -> {
713  eventPublisher.addSubscriber(event.toString(), subscriber);
714  });
715  }
716 
725  @Deprecated
726  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
727  eventPublisher.addSubscriber(eventName, subscriber);
728  }
729 
736  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
737  eventPublisher.removeSubscriber(eventName, subscriber);
738  }
739 
746  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
747  eventPublisher.removeSubscriber(eventNames, subscriber);
748  }
749 
756  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
757  eventTypes.forEach((Events event) -> {
758  eventPublisher.removeSubscriber(event.toString(), subscriber);
759  });
760  }
761 
770  public static boolean isValidName(String caseName) {
771  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
772  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
773  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
774  }
775 
800  @Deprecated
801  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
802  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
803  }
804 
824  @Messages({
825  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
826  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
827  })
828  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
829  if (caseDetails.getCaseDisplayName().isEmpty()) {
830  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
831  }
832  if (caseDir.isEmpty()) {
833  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
834  }
835  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
836  }
837 
851  @Messages({
852  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
853  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
854  })
855  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
857  try {
858  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
859  } catch (CaseMetadataException ex) {
860  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
861  }
863  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
864  }
865  openAsCurrentCase(new Case(metadata), false);
866  }
867 
873  public static boolean isCaseOpen() {
874  return currentCase != null;
875  }
876 
884  public static Case getCurrentCase() {
885  try {
886  return getCurrentCaseThrows();
887  } catch (NoCurrentCaseException ex) {
888  /*
889  * Throw a runtime exception, since this is a programming error.
890  */
891  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
892  }
893  }
894 
910  /*
911  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
912  * case method.
913  */
914  Case openCase = currentCase;
915  if (openCase == null) {
916  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
917  } else {
918  return openCase;
919  }
920  }
921 
930  @Messages({
931  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
932  "Case.progressIndicatorTitle.closingCase=Closing Case"
933  })
934  public static void closeCurrentCase() throws CaseActionException {
935  synchronized (caseActionSerializationLock) {
936  if (null == currentCase) {
937  return;
938  }
939  Case closedCase = currentCase;
940  try {
941  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
942  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
943  closedCase.doCloseCaseAction();
944  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
945  } catch (CaseActionException ex) {
946  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
947  throw ex;
948  } finally {
949  currentCase = null;
952  }
953  }
954  }
955  }
956 
965  public static void deleteCurrentCase() throws CaseActionException {
966  synchronized (caseActionSerializationLock) {
967  if (null == currentCase) {
968  return;
969  }
970  CaseMetadata metadata = currentCase.getMetadata();
972  deleteCase(metadata);
973  }
974  }
975 
986  @Messages({
987  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
988  })
989  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
990  synchronized (caseActionSerializationLock) {
991  if (null == currentCase) {
992  return;
993  }
994 
995  /*
996  * Close the current case to release the shared case lock.
997  */
998  CaseMetadata caseMetadata = currentCase.getMetadata();
1000 
1001  /*
1002  * Re-open the case with an exclusive case lock, delete the data
1003  * source, and close the case again, releasing the exclusive case
1004  * lock.
1005  */
1006  Case theCase = new Case(caseMetadata);
1007  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
1008 
1009  /*
1010  * Re-open the case with a shared case lock.
1011  */
1012  openAsCurrentCase(new Case(caseMetadata), false);
1013  }
1014  }
1015 
1027  @Messages({
1028  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
1029  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
1030  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
1031  })
1032  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
1033  synchronized (caseActionSerializationLock) {
1034  if (null != currentCase) {
1035  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
1036  }
1037  }
1038 
1039  ProgressIndicator progressIndicator;
1041  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
1042  } else {
1043  progressIndicator = new LoggingProgressIndicator();
1044  }
1045  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1046  try {
1047  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1048  deleteSingleUserCase(metadata, progressIndicator);
1049  } else {
1050  try {
1051  deleteMultiUserCase(metadata, progressIndicator);
1052  } catch (InterruptedException ex) {
1053  /*
1054  * Note that task cancellation is not currently supported
1055  * for this code path, so this catch block is not expected
1056  * to be executed.
1057  */
1058  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
1059  }
1060  }
1061  } finally {
1062  progressIndicator.finish();
1063  }
1064  }
1065 
1076  @Messages({
1077  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1078  "Case.progressIndicatorTitle.openingCase=Opening Case",
1079  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
1080  })
1081  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
1082  synchronized (caseActionSerializationLock) {
1083  if (null != currentCase) {
1084  try {
1085  closeCurrentCase();
1086  } catch (CaseActionException ex) {
1087  /*
1088  * Notify the user and continue (the error has already been
1089  * logged in closeCurrentCase.
1090  */
1091  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
1092  }
1093  }
1094  try {
1095  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1096  String progressIndicatorTitle;
1098  if (isNewCase) {
1099  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
1100  openCaseAction = newCurrentCase::create;
1101  } else {
1102  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
1103  openCaseAction = newCurrentCase::open;
1104  }
1105  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
1106  currentCase = newCurrentCase;
1107  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1109  updateGUIForCaseOpened(newCurrentCase);
1110  }
1111  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
1112  } catch (CaseActionCancelledException ex) {
1113  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
1114  throw ex;
1115  } catch (CaseActionException ex) {
1116  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
1117  throw ex;
1118  }
1119  }
1120  }
1121 
1130  private static String displayNameToUniqueName(String caseDisplayName) {
1131  /*
1132  * Replace all non-ASCII characters.
1133  */
1134  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
1135 
1136  /*
1137  * Replace all control characters.
1138  */
1139  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
1140 
1141  /*
1142  * Replace /, \, :, ?, space, ' ".
1143  */
1144  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
1145 
1146  /*
1147  * Make it all lowercase.
1148  */
1149  uniqueCaseName = uniqueCaseName.toLowerCase();
1150 
1151  /*
1152  * Add a time stamp for uniqueness.
1153  */
1154  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
1155  Date date = new Date();
1156  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
1157 
1158  return uniqueCaseName;
1159  }
1160 
1170  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
1171  /*
1172  * Check the case directory path and permissions. The case directory may
1173  * already exist.
1174  */
1175  File caseDir = new File(caseDirPath);
1176  if (caseDir.exists()) {
1177  if (caseDir.isFile()) {
1178  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
1179  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
1180  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
1181  }
1182  }
1183 
1184  /*
1185  * Create the case directory, if it does not already exist.
1186  */
1187  if (!caseDir.mkdirs()) {
1188  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
1189  }
1190 
1191  /*
1192  * Create the subdirectories of the case directory, if they do not
1193  * already exist. Note that multi-user cases get an extra layer of
1194  * subdirectories, one subdirectory per application host machine.
1195  */
1196  String hostPathComponent = "";
1197  if (caseType == CaseType.MULTI_USER_CASE) {
1198  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
1199  }
1200 
1201  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
1202  if (!exportDir.toFile().mkdirs()) {
1203  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
1204  }
1205 
1206  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
1207  if (!logsDir.toFile().mkdirs()) {
1208  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
1209  }
1210 
1211  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
1212  if (!cacheDir.toFile().mkdirs()) {
1213  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
1214  }
1215 
1216  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
1217  if (!moduleOutputDir.toFile().mkdirs()) {
1218  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
1219  }
1220 
1221  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1222  if (!reportsDir.toFile().mkdirs()) {
1223  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1224  }
1225  }
1226 
1234  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1235  Map<Long, String> imgPaths = new HashMap<>();
1236  try {
1237  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1238  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1239  if (entry.getValue().size() > 0) {
1240  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1241  }
1242  }
1243  } catch (TskCoreException ex) {
1244  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1245  }
1246  return imgPaths;
1247  }
1248 
1259  @Messages({
1260  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1261  })
1262  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1263  try {
1264  Path caseDirPath = Paths.get(caseDir);
1265  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1266  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1267  return lock;
1268  } catch (InterruptedException ex) {
1269  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1270  } catch (CoordinationServiceException ex) {
1271  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1272  }
1273  }
1274 
1275  private static String getNameForTitle() {
1276  //Method should become unnecessary once technical debt story 3334 is done.
1277  if (UserPreferences.getAppName().equals(Version.getName())) {
1278  //Available version number is version number for this application
1279  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1280  } else {
1281  return UserPreferences.getAppName();
1282  }
1283  }
1284 
1288  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1289  /*
1290  * If the case database was upgraded for a new schema and a backup
1291  * database was created, notify the user.
1292  */
1293  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1294  String backupDbPath = caseDb.getBackupDatabasePath();
1295  if (null != backupDbPath) {
1296  JOptionPane.showMessageDialog(
1297  mainFrame,
1298  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1299  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1300  JOptionPane.INFORMATION_MESSAGE);
1301  }
1302 
1303  /*
1304  * Look for the files for the data sources listed in the case database
1305  * and give the user the opportunity to locate any that are missing.
1306  */
1307  Map<Long, String> imgPaths = getImagePaths(caseDb);
1308  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1309  long obj_id = entry.getKey();
1310  String path = entry.getValue();
1311  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1312  if (!fileExists) {
1313  try {
1314  // Using invokeAndWait means that the dialog will
1315  // open on the EDT but this thread will wait for an
1316  // answer. Using invokeLater would cause this loop to
1317  // end before all of the dialogs appeared.
1318  SwingUtilities.invokeAndWait(new Runnable() {
1319  @Override
1320  public void run() {
1321  int response = JOptionPane.showConfirmDialog(
1322  mainFrame,
1323  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1324  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1325  JOptionPane.YES_NO_OPTION);
1326  if (response == JOptionPane.YES_OPTION) {
1327  MissingImageDialog.makeDialog(obj_id, caseDb);
1328  } else {
1329  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1330 
1331  }
1332  }
1333 
1334  });
1335  } catch (InterruptedException | InvocationTargetException ex) {
1336  logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
1337  }
1338  }
1339  }
1340 
1341  /*
1342  * Enable the case-specific actions.
1343  */
1344  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1345  CallableSystemAction.get(OpenHostsAction.class).setEnabled(true);
1346  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1347  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1348  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1349  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1350  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1351  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1352  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1353  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1354  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1355 
1356  /*
1357  * Add the case to the recent cases tracker that supplies a list of
1358  * recent cases to the recent cases menu item and the open/create case
1359  * dialog.
1360  */
1361  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1362  final boolean hasData = newCurrentCase.hasData();
1363 
1364  SwingUtilities.invokeLater(() -> {
1365  /*
1366  * Open the top components (windows within the main application
1367  * window).
1368  *
1369  * Note: If the core windows are not opened here, they will be
1370  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1371  * method on a DATA_SOURCE_ADDED event.
1372  */
1373  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1374  if (hasData) {
1376  } else {
1377  //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible.
1379  }
1380  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
1381 
1382  /*
1383  * Reset the main window title to:
1384  *
1385  * [curent case display name] - [application name].
1386  */
1387  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1388  });
1389  }
1390 
1391  /*
1392  * Update the GUI to to reflect the lack of a current case.
1393  */
1394  private static void updateGUIForCaseClosed() {
1396  SwingUtilities.invokeLater(() -> {
1397  /*
1398  * Close the top components (windows within the main application
1399  * window).
1400  */
1402 
1403  /*
1404  * Disable the case-specific menu items.
1405  */
1406  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1407  CallableSystemAction.get(OpenHostsAction.class).setEnabled(false);
1408  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1409  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1410  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1411  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1412  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1413  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1414  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1415  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1416  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1417 
1418  /*
1419  * Clear the notifications in the notfier component in the lower
1420  * right hand corner of the main application window.
1421  */
1423 
1424  /*
1425  * Reset the main window title to be just the application name,
1426  * instead of [curent case display name] - [application name].
1427  */
1428  mainFrame.setTitle(getNameForTitle());
1429  });
1430  }
1431  }
1432 
1438  public SleuthkitCase getSleuthkitCase() {
1439  return this.caseDb;
1440  }
1441 
1448  return caseServices;
1449  }
1450 
1457  return metadata.getCaseType();
1458  }
1459 
1465  public String getCreatedDate() {
1466  return metadata.getCreatedDate();
1467  }
1468 
1474  public String getName() {
1475  return metadata.getCaseName();
1476  }
1477 
1483  public String getDisplayName() {
1484  return metadata.getCaseDisplayName();
1485  }
1486 
1492  public String getNumber() {
1493  return metadata.getCaseNumber();
1494  }
1495 
1501  public String getExaminer() {
1502  return metadata.getExaminer();
1503  }
1504 
1510  public String getExaminerPhone() {
1511  return metadata.getExaminerPhone();
1512  }
1513 
1519  public String getExaminerEmail() {
1520  return metadata.getExaminerEmail();
1521  }
1522 
1528  public String getCaseNotes() {
1529  return metadata.getCaseNotes();
1530  }
1531 
1537  public String getCaseDirectory() {
1538  return metadata.getCaseDirectory();
1539  }
1540 
1549  public String getOutputDirectory() {
1550  String caseDirectory = getCaseDirectory();
1551  Path hostPath;
1552  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1553  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1554  } else {
1555  hostPath = Paths.get(caseDirectory);
1556  }
1557  if (!hostPath.toFile().exists()) {
1558  hostPath.toFile().mkdirs();
1559  }
1560  return hostPath.toString();
1561  }
1562 
1566  private Path getBaseSystemTempPath() {
1567  return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName());
1568  }
1569 
1576  public String getTempDirectory() {
1577  // NOTE: UserPreferences may also be affected by changes in this method.
1578  // See JIRA-7505 for more information.
1579  Path basePath = null;
1580  // get base temp path for the case based on user preference
1582  case CUSTOM:
1583  String customDirectory = UserMachinePreferences.getCustomTempDirectory();
1584  basePath = (StringUtils.isBlank(customDirectory))
1585  ? null
1586  : Paths.get(customDirectory, APP_NAME, getName());
1587  break;
1588  case CASE:
1589  basePath = Paths.get(getCaseDirectory());
1590  break;
1591  case SYSTEM:
1592  default:
1593  // at this level, if the case directory is specified for a temp
1594  // directory, return the system temp directory instead.
1595  basePath = getBaseSystemTempPath();
1596  break;
1597  }
1598 
1599  basePath = basePath == null ? getBaseSystemTempPath() : basePath;
1600 
1601  // get sub directories based on multi user vs. single user
1602  Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType()))
1603  ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER)
1604  : Paths.get(TEMP_FOLDER);
1605 
1606  File caseTempDir = basePath
1607  .resolve(caseRelPath)
1608  .toFile();
1609 
1610  // ensure directory exists
1611  if (!caseTempDir.exists()) {
1612  caseTempDir.mkdirs();
1613  }
1614 
1615  return caseTempDir.getAbsolutePath();
1616  }
1617 
1624  public String getCacheDirectory() {
1625  return getOrCreateSubdirectory(CACHE_FOLDER);
1626  }
1627 
1634  public String getExportDirectory() {
1635  return getOrCreateSubdirectory(EXPORT_FOLDER);
1636  }
1637 
1644  public String getLogDirectoryPath() {
1645  return getOrCreateSubdirectory(LOG_FOLDER);
1646  }
1647 
1654  public String getReportDirectory() {
1655  return getOrCreateSubdirectory(REPORTS_FOLDER);
1656  }
1657 
1664  public String getConfigDirectory() {
1665  return getOrCreateSubdirectory(CONFIG_FOLDER);
1666  }
1667 
1674  public String getModuleDirectory() {
1675  return getOrCreateSubdirectory(MODULE_FOLDER);
1676  }
1677 
1686  Path path = Paths.get(getModuleDirectory());
1688  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1689  } else {
1690  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1691  }
1692  }
1693 
1703  public List<Content> getDataSources() throws TskCoreException {
1704  return caseDb.getRootObjects();
1705  }
1706 
1712  public Set<TimeZone> getTimeZones() {
1713  Set<TimeZone> timezones = new HashSet<>();
1714  String query = "SELECT time_zone FROM data_source_info";
1715  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1716  ResultSet timeZoneSet = dbQuery.getResultSet();
1717  while (timeZoneSet.next()) {
1718  String timeZone = timeZoneSet.getString("time_zone");
1719  if (timeZone != null && !timeZone.isEmpty()) {
1720  timezones.add(TimeZone.getTimeZone(timeZone));
1721  }
1722  }
1723  } catch (TskCoreException | SQLException ex) {
1724  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1725  }
1726  return timezones;
1727  }
1728 
1735  public String getTextIndexName() {
1736  return getMetadata().getTextIndexName();
1737  }
1738 
1744  public boolean hasData() {
1745  return hasData;
1746  }
1747 
1753  public boolean hasDataSource() {
1754  return hasDataSource;
1755  }
1756 
1767  public void notifyAddingDataSource(UUID eventId) {
1768  hasDataSource = true;
1769  hasData = true;
1770  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1771  }
1772 
1783  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1784  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1785  }
1786 
1798  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1799  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1800  }
1801 
1811  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1812  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1813  }
1814 
1822  public void notifyContentTagAdded(ContentTag newTag) {
1823  notifyContentTagAdded(newTag, null);
1824  }
1825 
1835  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1836  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1837  }
1838 
1846  public void notifyContentTagDeleted(ContentTag deletedTag) {
1847  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1848  }
1849 
1857  public void notifyTagDefinitionChanged(String changedTagName) {
1858  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1859  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1860  }
1861 
1872  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1873  try {
1874  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1875  } catch (NoCurrentCaseException ex) {
1876  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1877  }
1878  }
1879 
1887  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1888  notifyBlackBoardArtifactTagAdded(newTag, null);
1889  }
1890 
1900  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1901  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1902  }
1903 
1911  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1912  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1913  }
1914 
1926  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1927  addReport(localPath, srcModuleName, reportName, null);
1928  }
1929 
1944  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1945  String normalizedLocalPath;
1946  try {
1947  if (localPath.toLowerCase().contains("http:")) {
1948  normalizedLocalPath = localPath;
1949  } else {
1950  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1951  }
1952  } catch (InvalidPathException ex) {
1953  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1954  throw new TskCoreException(errorMsg, ex);
1955  }
1956  hasData = true;
1957 
1958  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1959  eventPublisher.publish(new ReportAddedEvent(report));
1960  return report;
1961  }
1962 
1971  public List<Report> getAllReports() throws TskCoreException {
1972  return this.caseDb.getAllReports();
1973  }
1974 
1983  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1984  for (Report report : reports) {
1985  this.caseDb.deleteReport(report);
1986  }
1987 
1988  try {
1989  hasData = dbHasData();
1990  } catch (TskCoreException ex) {
1991  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
1992  }
1993 
1994  for (Report report : reports) {
1995  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1996  }
1997  }
1998 
2005  return metadata;
2006  }
2007 
2015  @Messages({
2016  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
2017  })
2018  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
2019  CaseDetails oldCaseDetails = metadata.getCaseDetails();
2020  try {
2021  metadata.setCaseDetails(caseDetails);
2022  } catch (CaseMetadataException ex) {
2023  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
2024  }
2025  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2026  try {
2027  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
2028  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
2029  CaseNodeData.writeCaseNodeData(nodeData);
2030  } catch (CaseNodeDataException | InterruptedException ex) {
2031  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2032  }
2033  }
2034  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
2035  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
2036  }
2037  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
2038  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
2039  }
2040  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2041  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
2042  }
2043  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
2044  if (RuntimeProperties.runningWithGUI()) {
2045  SwingUtilities.invokeLater(() -> {
2046  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
2047  try {
2048  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
2049  } catch (Exception ex) {
2050  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
2051  }
2052  });
2053  }
2054  }
2055 
2068  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
2069  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
2070  }
2071 
2077  private Case(CaseMetadata caseMetaData) {
2078  metadata = caseMetaData;
2079  sleuthkitEventListener = new SleuthkitEventListener();
2080  }
2081 
2111  @Messages({
2112  "Case.progressIndicatorCancelButton.label=Cancel",
2113  "Case.progressMessage.preparing=Preparing...",
2114  "Case.progressMessage.cancelling=Cancelling...",
2115  "Case.exceptionMessage.cancelled=Cancelled.",
2116  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
2117  })
2118  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
2119  /*
2120  * Create and start either a GUI progress indicator (with or without a
2121  * cancel button) or a logging progress indicator.
2122  */
2123  CancelButtonListener cancelButtonListener = null;
2124  ProgressIndicator progressIndicator;
2126  if (allowCancellation) {
2127  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
2128  progressIndicator = new ModalDialogProgressIndicator(
2129  mainFrame,
2130  progressIndicatorTitle,
2131  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2132  Bundle.Case_progressIndicatorCancelButton_label(),
2133  cancelButtonListener);
2134  } else {
2135  progressIndicator = new ModalDialogProgressIndicator(
2136  mainFrame,
2137  progressIndicatorTitle);
2138  }
2139  } else {
2140  progressIndicator = new LoggingProgressIndicator();
2141  }
2142  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2143 
2144  /*
2145  * Do the case action in the single thread in the case action executor.
2146  * If the case is a multi-user case, a case lock is acquired and held
2147  * until explictly released and an exclusive case resources lock is
2148  * aquired and held for the duration of the action.
2149  */
2150  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
2151  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
2152  Future<Void> future = caseActionExecutor.submit(() -> {
2153  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2154  caseAction.execute(progressIndicator, additionalParams);
2155  } else {
2156  acquireCaseLock(caseLockType);
2157  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2158  if (null == resourcesLock) {
2159  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2160  }
2161  caseAction.execute(progressIndicator, additionalParams);
2162  } catch (CaseActionException ex) {
2163  releaseCaseLock();
2164  throw ex;
2165  }
2166  }
2167  return null;
2168  });
2169  if (null != cancelButtonListener) {
2170  cancelButtonListener.setCaseActionFuture(future);
2171  }
2172 
2173  /*
2174  * Wait for the case action task to finish.
2175  */
2176  try {
2177  future.get();
2178  } catch (InterruptedException discarded) {
2179  /*
2180  * The thread this method is running in has been interrupted.
2181  */
2182  if (null != cancelButtonListener) {
2183  cancelButtonListener.actionPerformed(null);
2184  } else {
2185  future.cancel(true);
2186  }
2187  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2188  } catch (CancellationException discarded) {
2189  /*
2190  * The case action has been cancelled.
2191  */
2192  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2193  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2194  } catch (ExecutionException ex) {
2195  /*
2196  * The case action has thrown an exception.
2197  */
2198  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2199  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
2200  } finally {
2201  progressIndicator.finish();
2202  }
2203  }
2204 
2220  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2221  assert (additionalParams == null);
2222  try {
2224  createCaseDirectoryIfDoesNotExist(progressIndicator);
2226  switchLoggingToCaseLogsDirectory(progressIndicator);
2228  saveCaseMetadataToFile(progressIndicator);
2230  createCaseNodeData(progressIndicator);
2233  createCaseDatabase(progressIndicator);
2235  openCaseLevelServices(progressIndicator);
2237  openAppServiceCaseResources(progressIndicator, true);
2239  openCommunicationChannels(progressIndicator);
2240  return null;
2241 
2242  } catch (CaseActionException ex) {
2243  /*
2244  * Cancellation or failure. The sleep is a little hack to clear the
2245  * interrupted flag for this thread if this is a cancellation
2246  * scenario, so that the clean up can run to completion in the
2247  * current thread.
2248  */
2249  try {
2250  Thread.sleep(1);
2251  } catch (InterruptedException discarded) {
2252  }
2253  close(progressIndicator);
2254  throw ex;
2255  }
2256  }
2257 
2272  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2273  assert (additionalParams == null);
2274  try {
2276  switchLoggingToCaseLogsDirectory(progressIndicator);
2278  updateCaseNodeData(progressIndicator);
2280  deleteTempfilesFromCaseDirectory(progressIndicator);
2282  openCaseDataBase(progressIndicator);
2284  openCaseLevelServices(progressIndicator);
2286  openAppServiceCaseResources(progressIndicator, false);
2288  openCommunicationChannels(progressIndicator);
2291  return null;
2292 
2293  } catch (CaseActionException ex) {
2294  /*
2295  * Cancellation or failure. The sleep is a little hack to clear the
2296  * interrupted flag for this thread if this is a cancellation
2297  * scenario, so that the clean up can run to completion in the
2298  * current thread.
2299  */
2300  try {
2301  Thread.sleep(1);
2302  } catch (InterruptedException discarded) {
2303  }
2304  close(progressIndicator);
2305  throw ex;
2306  }
2307  }
2308 
2318  @Messages({
2319  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2320  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2321  })
2323  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2324  backgroundOpenFileSystemsFuture.cancel(true);
2325  }
2326 
2328  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2329  }
2330 
2335  private static class BackgroundOpenFileSystemsTask implements Runnable {
2336 
2337  private final SleuthkitCase tskCase;
2338  private final String caseName;
2339  private final long MAX_IMAGE_THRESHOLD = 100;
2341 
2350  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2351  this.tskCase = tskCase;
2352  this.progressIndicator = progressIndicator;
2353  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2354  }
2355 
2363  private void checkIfCancelled() throws InterruptedException {
2364  if (Thread.interrupted()) {
2365  throw new InterruptedException();
2366  }
2367  }
2368 
2374  private List<Image> getImages() {
2375  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2376  try {
2377  return this.tskCase.getImages();
2378  } catch (TskCoreException ex) {
2379  logger.log(
2380  Level.SEVERE,
2381  String.format("Could not obtain images while opening case: %s.", caseName),
2382  ex);
2383 
2384  return null;
2385  }
2386  }
2387 
2397  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2398  byte[] tempBuff = new byte[512];
2399 
2400  for (Image image : images) {
2401  String imageStr = image.getName();
2402 
2403  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2404 
2405  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2406  checkIfCancelled();
2407  for (FileSystem fileSystem : fileSystems) {
2408  fileSystem.read(tempBuff, 0, 512);
2409  checkIfCancelled();
2410  }
2411 
2412  }
2413  }
2414 
2415  @Override
2416  public void run() {
2417  try {
2418  checkIfCancelled();
2419  List<Image> images = getImages();
2420  if (images == null) {
2421  return;
2422  }
2423 
2424  if (images.size() > MAX_IMAGE_THRESHOLD) {
2425  // If we have a large number of images, don't try to preload anything
2426  logger.log(
2427  Level.INFO,
2428  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2429  return;
2430  }
2431 
2432  checkIfCancelled();
2433  openFileSystems(images);
2434  } catch (InterruptedException ex) {
2435  logger.log(
2436  Level.INFO,
2437  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2438  } catch (Exception ex) {
2439  // Exception firewall
2440  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2441  }
2442  }
2443 
2444  }
2445 
2460  @Messages({
2461  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2462  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2463  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2464  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2465  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2466  assert (additionalParams instanceof Long);
2467  open(progressIndicator, null);
2468  try {
2469  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2470  Long dataSourceObjectID = (Long) additionalParams;
2471  try {
2472  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2473  if (dataSource == null) {
2474  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2475  }
2476  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2477  } catch (TskDataException | TskCoreException ex) {
2478  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2479  }
2480  try {
2481  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2482  } catch (KeywordSearchServiceException ex) {
2483  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2484  }
2485  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2486  return null;
2487  } finally {
2488  close(progressIndicator);
2489  releaseCaseLock();
2490  }
2491  }
2492 
2503  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2504 
2505  if (portableCaseFolder.exists()) {
2506  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2507  }
2508  if (!portableCaseFolder.mkdirs()) {
2509  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2510  }
2511 
2512  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2514  try {
2515  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2516  caseName, details, metadata);
2517  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2518  } catch (CaseMetadataException ex) {
2519  throw new TskCoreException("Error creating case metadata", ex);
2520  }
2521 
2522  // Create the Sleuthkit case
2523  SleuthkitCase portableSleuthkitCase;
2524  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2525  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2526 
2527  return portableSleuthkitCase;
2528  }
2529 
2540  if (Thread.currentThread().isInterrupted()) {
2541  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2542  }
2543  }
2544 
2555  @Messages({
2556  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2557  })
2558  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2559  /*
2560  * TODO (JIRA-2180): Always create the case directory as part of the
2561  * case creation process.
2562  */
2563  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2564  if (new File(metadata.getCaseDirectory()).exists() == false) {
2565  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2566  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2567  }
2568  }
2569 
2576  @Messages({
2577  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2578  })
2579  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2580  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2582  }
2583 
2595  @Messages({
2596  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2597  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2598  })
2599  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2600  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2601  try {
2602  this.metadata.writeToFile();
2603  } catch (CaseMetadataException ex) {
2604  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2605  }
2606  }
2607 
2619  @Messages({
2620  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2621  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2622  })
2623  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2625  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2626  try {
2627  CaseNodeData.createCaseNodeData(metadata);
2628  } catch (CaseNodeDataException | InterruptedException ex) {
2629  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2630  }
2631  }
2632  }
2633 
2645  @Messages({
2646  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2647  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2648  })
2649  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2651  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2652  try {
2654  nodeData.setLastAccessDate(new Date());
2655  CaseNodeData.writeCaseNodeData(nodeData);
2656  } catch (CaseNodeDataException | InterruptedException ex) {
2657  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2658  }
2659  }
2660  }
2661 
2667  @Messages({
2668  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2669  })
2670  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2671  /*
2672  * Clear the temp subdirectory of the case directory.
2673  */
2674  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2675  FileUtil.deleteDir(new File(this.getTempDirectory()));
2676  }
2677 
2689  @Messages({
2690  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2691  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2692  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2693  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2694  })
2695  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2696  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2697  try {
2698  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2699  /*
2700  * For single-user cases, the case database is a SQLite database
2701  * with a standard name, physically located in the case
2702  * directory.
2703  */
2704  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2705  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2706  } else {
2707  /*
2708  * For multi-user cases, the case database is a PostgreSQL
2709  * database with a name derived from the case display name,
2710  * physically located on the PostgreSQL database server.
2711  */
2712  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2713  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2714  }
2715  } catch (TskCoreException ex) {
2716  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2717  } catch (UserPreferencesException ex) {
2718  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2719  } catch (CaseMetadataException ex) {
2720  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2721  }
2722  }
2723 
2735  @Messages({
2736  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2737  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2738  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2739  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2740  })
2741  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2742  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2743  try {
2744  String databaseName = metadata.getCaseDatabaseName();
2745  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2746  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
2748  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2749  } else {
2750  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2751  }
2753  } catch (TskUnsupportedSchemaVersionException ex) {
2754  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2755  } catch (UserPreferencesException ex) {
2756  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2757  } catch (TskCoreException ex) {
2758  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2759  }
2760  }
2761 
2768  @Messages({
2769  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2770  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2771  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2772  this.caseServices = new Services(caseDb);
2773  /*
2774  * RC Note: JM put this initialization here. I'm not sure why. However,
2775  * my attempt to put it in the openCaseDatabase method seems to lead to
2776  * intermittent unchecked exceptions concerning a missing subscriber.
2777  */
2778  caseDb.registerForEvents(sleuthkitEventListener);
2779  }
2780 
2793  @NbBundle.Messages({
2794  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2795  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2796  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2797  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2798  })
2799  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2800  /*
2801  * Each service gets its own independently cancellable/interruptible
2802  * task, running in a named thread managed by an executor service, with
2803  * its own progress indicator. This allows for cancellation of the
2804  * opening of case resources for individual services. It also makes it
2805  * possible to ensure that each service task completes before the next
2806  * one starts by awaiting termination of the executor service.
2807  */
2808  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2809 
2810  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2811  )) {
2812  /*
2813  * Create a progress indicator for the task and start the task. If
2814  * running with a GUI, the progress indicator will be a dialog box
2815  * with a Cancel button.
2816  */
2817  CancelButtonListener cancelButtonListener = null;
2818  ProgressIndicator appServiceProgressIndicator;
2820  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2821  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2822  mainFrame,
2823  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2824  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2825  Bundle.Case_progressIndicatorCancelButton_label(),
2826  cancelButtonListener);
2827  } else {
2828  appServiceProgressIndicator = new LoggingProgressIndicator();
2829  }
2830  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2831  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2832  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2833  threadNameSuffix = threadNameSuffix.toLowerCase();
2834  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2835  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2836  Future<Void> future = executor.submit(() -> {
2837  service.openCaseResources(context);
2838  return null;
2839  });
2840  if (null != cancelButtonListener) {
2841  cancelButtonListener.setCaseContext(context);
2842  cancelButtonListener.setCaseActionFuture(future);
2843  }
2844 
2845  /*
2846  * Wait for the task to either be completed or
2847  * cancelled/interrupted, or for the opening of the case to be
2848  * cancelled.
2849  */
2850  try {
2851  future.get();
2852  } catch (InterruptedException discarded) {
2853  /*
2854  * The parent create/open case task has been cancelled.
2855  */
2856  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()));
2857  future.cancel(true);
2858  } catch (CancellationException discarded) {
2859  /*
2860  * The opening of case resources by the application service has
2861  * been cancelled, so the executor service has thrown. Note that
2862  * there is no guarantee the task itself has responded to the
2863  * cancellation request yet.
2864  */
2865  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()));
2866  } catch (ExecutionException ex) {
2867  /*
2868  * An exception was thrown while executing the task. The
2869  * case-specific application service resources are not
2870  * essential. Log an error and notify the user if running the
2871  * desktop GUI, but do not throw.
2872  */
2873  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2875  SwingUtilities.invokeLater(() -> {
2876  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2877  });
2878  }
2879  } finally {
2880  /*
2881  * Shut down the executor service and wait for it to finish.
2882  * This ensures that the task has finished. Without this, it
2883  * would be possible to start the next task before the current
2884  * task responded to a cancellation request.
2885  */
2887  appServiceProgressIndicator.finish();
2888  }
2890  }
2891  }
2892 
2904  @Messages({
2905  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2906  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2907  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2908  })
2909  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2910  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2911  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2912  try {
2913  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2915  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2916  } catch (AutopsyEventException ex) {
2917  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2918  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2919  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2920  }
2921  }
2922  }
2923 
2938  private void doCloseCaseAction() throws CaseActionException {
2939  /*
2940  * Set up either a GUI progress indicator without a Cancel button or a
2941  * logging progress indicator.
2942  */
2943  ProgressIndicator progressIndicator;
2945  progressIndicator = new ModalDialogProgressIndicator(
2946  mainFrame,
2947  Bundle.Case_progressIndicatorTitle_closingCase());
2948  } else {
2949  progressIndicator = new LoggingProgressIndicator();
2950  }
2951  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2952 
2953  /*
2954  * Closing a case is always done in the same non-UI thread that
2955  * opened/created the case. If the case is a multi-user case, this
2956  * ensures that case lock that is held as long as the case is open is
2957  * released in the same thread in which it was acquired, as is required
2958  * by the coordination service.
2959  */
2960  Future<Void> future = caseActionExecutor.submit(() -> {
2961  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2962  close(progressIndicator);
2963  } else {
2964  /*
2965  * Acquire an exclusive case resources lock to ensure only one
2966  * node at a time can create/open/upgrade/close the case
2967  * resources.
2968  */
2969  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2970  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2971  if (null == resourcesLock) {
2972  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2973  }
2974  close(progressIndicator);
2975  } finally {
2976  /*
2977  * Always release the case directory lock that was acquired
2978  * when the case was opened.
2979  */
2980  releaseCaseLock();
2981  }
2982  }
2983  return null;
2984  });
2985 
2986  try {
2987  future.get();
2988  } catch (InterruptedException | CancellationException unused) {
2989  /*
2990  * The wait has been interrupted by interrupting the thread running
2991  * this method. Not allowing cancellation of case closing, so ignore
2992  * the interrupt. Likewise, cancellation of the case closing task is
2993  * not supported.
2994  */
2995  } catch (ExecutionException ex) {
2996  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2997  } finally {
2998  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2999  progressIndicator.finish();
3000  }
3001  }
3002 
3008  @Messages({
3009  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
3010  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
3011  "Case.progressMessage.closingCaseDatabase=Closing case database..."
3012  })
3013  private void close(ProgressIndicator progressIndicator) {
3015 
3016  /*
3017  * Stop sending/receiving case events to and from other nodes if this is
3018  * a multi-user case.
3019  */
3020  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
3021  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
3022  if (null != collaborationMonitor) {
3023  collaborationMonitor.shutdown();
3024  }
3025  eventPublisher.closeRemoteEventChannel();
3026  }
3027 
3028  /*
3029  * Allow all registered application services providers to close
3030  * resources related to the case.
3031  */
3032  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
3034 
3035  /*
3036  * Close the case database.
3037  */
3038  if (null != caseDb) {
3039  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
3040  caseDb.unregisterForEvents(sleuthkitEventListener);
3041  caseDb.close();
3042  }
3043 
3047  deleteTempfilesFromCaseDirectory(progressIndicator);
3048 
3049  /*
3050  * Switch the log directory.
3051  */
3052  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
3054  }
3055 
3060  @Messages({
3061  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
3062  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
3063  })
3065  /*
3066  * Each service gets its own independently cancellable task, and thus
3067  * its own task progress indicator.
3068  */
3069  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
3070  )) {
3071  ProgressIndicator progressIndicator;
3073  progressIndicator = new ModalDialogProgressIndicator(
3074  mainFrame,
3075  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
3076  } else {
3077  progressIndicator = new LoggingProgressIndicator();
3078  }
3079  progressIndicator.start(Bundle.Case_progressMessage_preparing());
3080  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
3081  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
3082  threadNameSuffix = threadNameSuffix.toLowerCase();
3083  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
3084  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
3085  Future<Void> future = executor.submit(() -> {
3086  service.closeCaseResources(context);
3087  return null;
3088  });
3089  try {
3090  future.get();
3091  } catch (InterruptedException ex) {
3092  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
3093  } catch (CancellationException ex) {
3094  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
3095  } catch (ExecutionException ex) {
3096  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
3098  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
3099  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
3100  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
3101  }
3102  } finally {
3104  progressIndicator.finish();
3105  }
3106  }
3107  }
3108 
3114  @Messages({
3115  "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.",
3116  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case."
3117  })
3118  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
3119  String caseDir = metadata.getCaseDirectory();
3120  try {
3121  CoordinationService coordinationService = CoordinationService.getInstance();
3122  caseLock = lockType == CaseLockType.SHARED
3123  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
3124  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
3125  if (caseLock == null) {
3126  if (lockType == CaseLockType.SHARED) {
3127  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
3128  } else {
3129  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
3130  }
3131  }
3132  } catch (InterruptedException | CoordinationServiceException ex) {
3133  if (lockType == CaseLockType.SHARED) {
3134  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
3135  } else {
3136  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
3137  }
3138  }
3139  }
3140 
3144  private void releaseCaseLock() {
3145  if (caseLock != null) {
3146  try {
3147  caseLock.release();
3148  caseLock = null;
3150  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
3151  }
3152  }
3153  }
3154 
3161  private String getOrCreateSubdirectory(String subDirectoryName) {
3162  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
3163  if (!subDirectory.exists()) {
3164  subDirectory.mkdirs();
3165  }
3166  return subDirectory.toString();
3167 
3168  }
3169 
3181  @Messages({
3182  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
3183  })
3184  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3185  boolean errorsOccurred = false;
3186  try {
3187  deleteTextIndex(metadata, progressIndicator);
3188  } catch (KeywordSearchServiceException ex) {
3189  errorsOccurred = true;
3190  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
3191  }
3192 
3193  try {
3194  deleteCaseDirectory(metadata, progressIndicator);
3195  } catch (CaseActionException ex) {
3196  errorsOccurred = true;
3197  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
3198  }
3199 
3200  deleteFromRecentCases(metadata, progressIndicator);
3201 
3202  if (errorsOccurred) {
3203  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3204  }
3205  }
3206 
3226  @Messages({
3227  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
3228  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
3229  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
3230  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
3231  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
3232  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
3233  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
3234  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
3235  })
3236  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
3237  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
3238  CoordinationService coordinationService;
3239  try {
3240  coordinationService = CoordinationService.getInstance();
3241  } catch (CoordinationServiceException ex) {
3242  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
3243  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
3244  }
3245 
3246  CaseNodeData caseNodeData;
3247  boolean errorsOccurred = false;
3248  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
3249  if (dirLock == null) {
3250  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
3251  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
3252  }
3253 
3254  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
3255  try {
3256  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
3257  } catch (CaseNodeDataException | InterruptedException ex) {
3258  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
3259  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
3260  }
3261 
3262  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
3263 
3264  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
3265  try {
3266  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
3267  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
3268  } catch (CoordinationServiceException ex) {
3269  if (!isNoNodeException(ex)) {
3270  errorsOccurred = true;
3271  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
3272  }
3273  } catch (InterruptedException ex) {
3274  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
3275  }
3276 
3277  } catch (CoordinationServiceException ex) {
3278  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
3279  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
3280  }
3281 
3282  if (!errorsOccurred) {
3283  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3284  try {
3285  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3286  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3287  } catch (CoordinationServiceException | InterruptedException ex) {
3288  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
3289  errorsOccurred = true;
3290  }
3291  }
3292 
3293  if (errorsOccurred) {
3294  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3295  }
3296  }
3297 
3322  @Beta
3323  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3324  boolean errorsOccurred = false;
3325  try {
3326  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3327  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3328  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3329  deleteFromRecentCases(metadata, progressIndicator);
3330  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3331  errorsOccurred = true;
3332  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
3333  } catch (KeywordSearchServiceException ex) {
3334  errorsOccurred = true;
3335  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
3336  } catch (CaseActionException ex) {
3337  errorsOccurred = true;
3338  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
3339  }
3340  return errorsOccurred;
3341  }
3342 
3363  @Messages({
3364  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3365  })
3366  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3367  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3368  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3369  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3370  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3371  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3372  Class.forName("org.postgresql.Driver"); //NON-NLS
3373  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3374  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3375  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3376  if (queryResult.next()) {
3377  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3378  statement.execute(deleteCommand);
3379  }
3380  }
3381  }
3383  }
3384  }
3385 
3401  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3403  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3404  deleteTextIndex(metadata, progressIndicator);
3406  }
3407  }
3408 
3418  @Messages({
3419  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3420  })
3421  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3422  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3423 
3424  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3425  )) {
3426  searchService.deleteTextIndex(metadata);
3427  }
3428  }
3429 
3444  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3445  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3446  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3447  deleteCaseDirectory(metadata, progressIndicator);
3449  }
3450  }
3451 
3461  @Messages({
3462  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3463  })
3464  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3465  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3466  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3467  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3468  }
3469  }
3470 
3478  @Messages({
3479  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3480  })
3481  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3483  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3484  SwingUtilities.invokeLater(() -> {
3485  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3486  });
3487  }
3488  }
3489 
3500  boolean isNodeNodeEx = false;
3501  Throwable cause = ex.getCause();
3502  if (cause != null) {
3503  String causeMessage = cause.getMessage();
3504  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3505  }
3506  return isNodeNodeEx;
3507  }
3508 
3520  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3521  try {
3522  caseNodeData.setDeletedFlag(flag);
3523  CaseNodeData.writeCaseNodeData(caseNodeData);
3524  } catch (CaseNodeDataException ex) {
3525  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);
3526 
3527  }
3528  }
3529 
3541  private void updateDataParameters() throws TskCoreException {
3542  hasDataSource = dbHasDataSource();
3543 
3544  if (!hasDataSource) {
3545  hasData = dbHasData();
3546  } else {
3547  hasData = true;
3548  }
3549  }
3550 
3558  private boolean dbHasDataSource() throws TskCoreException {
3559  String query = "SELECT count(*) AS count FROM (SELECT * FROM data_source_info LIMIT 1)t";
3560  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3561  ResultSet resultSet = dbQuery.getResultSet();
3562  if (resultSet.next()) {
3563  return resultSet.getLong("count") > 0;
3564  }
3565  return false;
3566  } catch (SQLException ex) {
3567  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3568  throw new TskCoreException("Error accessing case databse", ex);
3569  }
3570  }
3571 
3580  private boolean dbHasData() throws TskCoreException {
3581  // The LIMIT 1 in the subquery should limit the data returned and
3582  // make the overall query more efficent.
3583  String query = "SELECT SUM(cnt) total FROM "
3584  + "(SELECT COUNT(*) AS cnt FROM "
3585  + "(SELECT * FROM tsk_objects LIMIT 1)t "
3586  + "UNION ALL "
3587  + "SELECT COUNT(*) AS cnt FROM "
3588  + "(SELECT * FROM tsk_hosts LIMIT 1)r) s";
3589  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3590  ResultSet resultSet = dbQuery.getResultSet();
3591  if (resultSet.next()) {
3592  return resultSet.getLong("total") > 0;
3593  } else {
3594  return false;
3595  }
3596  } catch (SQLException ex) {
3597  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3598  throw new TskCoreException("Error accessing case databse", ex);
3599  }
3600  }
3601 
3610  private interface CaseAction<T, V, R> {
3611 
3622  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3623  }
3624 
3629  private enum CaseLockType {
3630  SHARED, EXCLUSIVE;
3631  }
3632 
3637  @ThreadSafe
3638  private final static class CancelButtonListener implements ActionListener {
3639 
3640  private final String cancellationMessage;
3641  @GuardedBy("this")
3642  private boolean cancelRequested;
3643  @GuardedBy("this")
3645  @GuardedBy("this")
3646  private Future<?> caseActionFuture;
3647 
3656  private CancelButtonListener(String cancellationMessage) {
3657  this.cancellationMessage = cancellationMessage;
3658  }
3659 
3665  private synchronized void setCaseContext(CaseContext caseContext) {
3666  this.caseContext = caseContext;
3667  /*
3668  * If the cancel button has already been pressed, pass the
3669  * cancellation on to the case context.
3670  */
3671  if (cancelRequested) {
3672  cancel();
3673  }
3674  }
3675 
3681  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3682  this.caseActionFuture = caseActionFuture;
3683  /*
3684  * If the cancel button has already been pressed, cancel the Future
3685  * of the task.
3686  */
3687  if (cancelRequested) {
3688  cancel();
3689  }
3690  }
3691 
3697  @Override
3698  public synchronized void actionPerformed(ActionEvent event) {
3699  cancel();
3700  }
3701 
3705  private void cancel() {
3706  /*
3707  * At a minimum, set the cancellation requested flag of this
3708  * listener.
3709  */
3710  this.cancelRequested = true;
3711  if (null != this.caseContext) {
3712  /*
3713  * Set the cancellation request flag and display the
3714  * cancellation message in the progress indicator for the case
3715  * context associated with this listener.
3716  */
3718  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3719  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3720  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3721  }
3722  }
3723  this.caseContext.requestCancel();
3724  }
3725  if (null != this.caseActionFuture) {
3726  /*
3727  * Cancel the Future of the task associated with this listener.
3728  * Note that the task thread will be interrupted if the task is
3729  * blocked.
3730  */
3731  this.caseActionFuture.cancel(true);
3732  }
3733  }
3734  }
3735 
3739  private static class TaskThreadFactory implements ThreadFactory {
3740 
3741  private final String threadName;
3742 
3743  private TaskThreadFactory(String threadName) {
3744  this.threadName = threadName;
3745  }
3746 
3747  @Override
3748  public Thread newThread(Runnable task) {
3749  return new Thread(task, threadName);
3750  }
3751 
3752  }
3753 
3761  @Deprecated
3762  public static String getAppName() {
3763  return UserPreferences.getAppName();
3764  }
3765 
3785  @Deprecated
3786  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3787  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3788  }
3789 
3810  @Deprecated
3811  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3812  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3813  }
3814 
3826  @Deprecated
3827  public static void open(String caseMetadataFilePath) throws CaseActionException {
3828  openAsCurrentCase(caseMetadataFilePath);
3829  }
3830 
3840  @Deprecated
3841  public void closeCase() throws CaseActionException {
3842  closeCurrentCase();
3843  }
3844 
3850  @Deprecated
3851  public static void invokeStartupDialog() {
3853  }
3854 
3868  @Deprecated
3869  public static String convertTimeZone(String timeZoneId) {
3870  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3871  }
3872 
3882  @Deprecated
3883  public static boolean pathExists(String filePath) {
3884  return new File(filePath).isFile();
3885  }
3886 
3895  @Deprecated
3896  public static String getAutopsyVersion() {
3897  return Version.getVersion();
3898  }
3899 
3907  @Deprecated
3908  public static boolean existsCurrentCase() {
3909  return isCaseOpen();
3910  }
3911 
3921  @Deprecated
3922  public static String getModulesOutputDirRelPath() {
3923  return "ModuleOutput"; //NON-NLS
3924  }
3925 
3935  @Deprecated
3936  public static PropertyChangeSupport
3938  return new PropertyChangeSupport(Case.class
3939  );
3940  }
3941 
3950  @Deprecated
3951  public String getModulesOutputDirAbsPath() {
3952  return getModuleDirectory();
3953  }
3954 
3969  @Deprecated
3970  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
3971  try {
3972  Image newDataSource = caseDb.getImageById(imgId);
3973  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
3974  return newDataSource;
3975  } catch (TskCoreException ex) {
3976  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
3977  }
3978  }
3979 
3987  @Deprecated
3988  public Set<TimeZone> getTimeZone() {
3989  return getTimeZones();
3990  }
3991 
4002  @Deprecated
4003  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
4004  deleteReports(reports);
4005  }
4006 
4007 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:188
List< Content > getDataSources()
Definition: Case.java:1703
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1846
Case(CaseMetadata caseMetaData)
Definition: Case.java:2077
static CaseType fromString(String typeName)
Definition: Case.java:234
final SleuthkitEventListener sleuthkitEventListener
Definition: Case.java:199
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:184
static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag)
Definition: Case.java:3520
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:828
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1911
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2272
CoordinationService.Lock caseLock
Definition: Case.java:197
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3323
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:3970
void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event)
Definition: Case.java:540
static synchronized IngestManager getInstance()
void deleteNode(CategoryNode category, String nodePath)
static final ExecutorService openFileSystemsExecutor
Definition: Case.java:192
static final String NO_NODE_ERROR_MSG_FRAGMENT
Definition: Case.java:186
void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event)
Definition: Case.java:617
void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event)
Definition: Case.java:589
void acquireCaseLock(CaseLockType lockType)
Definition: Case.java:3118
static boolean existsCurrentCase()
Definition: Case.java:3908
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:686
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:187
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List< BlackboardArtifactTag > removedTagList)
Definition: Case.java:1900
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:190
static final String EXPORT_FOLDER
Definition: Case.java:179
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:1170
void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event)
Definition: Case.java:555
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1857
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1835
static volatile Frame mainFrame
Definition: Case.java:193
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3869
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3184
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event)
Definition: Case.java:633
void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event)
Definition: Case.java:529
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
volatile ExecutorService caseActionExecutor
Definition: Case.java:196
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1872
static final String APP_NAME
Definition: Case.java:174
static final String CACHE_FOLDER
Definition: Case.java:178
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3444
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:2068
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1926
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1288
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1811
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3811
void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event)
Definition: Case.java:628
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:172
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:176
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:1032
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:4003
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3665
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:173
static boolean isValidName(String caseName)
Definition: Case.java:770
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2741
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2558
CollaborationMonitor collaborationMonitor
Definition: Case.java:200
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2909
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3922
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2599
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3401
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:2118
void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event)
Definition: Case.java:638
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3698
static final String MODULE_FOLDER
Definition: Case.java:183
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2770
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3786
synchronized void openRemoteEventChannel(String channelName)
void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event)
Definition: Case.java:606
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2623
void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event)
Definition: Case.java:643
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:1130
static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3481
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:855
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:746
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static boolean isNoNodeException(CoordinationServiceException ex)
Definition: Case.java:3499
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:3013
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1887
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3937
void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event)
Definition: Case.java:545
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:674
void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event)
Definition: Case.java:578
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3681
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2695
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:736
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2579
void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event)
Definition: Case.java:663
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3421
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2670
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1983
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1798
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1944
static boolean pathExists(String filePath)
Definition: Case.java:3883
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2503
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3827
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:1081
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1262
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:3161
boolean equalsName(String otherTypeName)
Definition: Case.java:292
static final String EVENT_CHANNEL_NAME
Definition: Case.java:177
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event)
Definition: Case.java:534
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:711
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:180
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2220
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:194
void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase)
Definition: Case.java:2799
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1767
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:726
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1822
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:185
void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event)
Definition: Case.java:566
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:801
void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event)
Definition: Case.java:648
static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3464
static synchronized DirectoryTreeTopComponent findInstance()
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1783
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3236
static final String CONFIG_FOLDER
Definition: Case.java:182
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:756
static final Object caseActionSerializationLock
Definition: Case.java:189
static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3366
void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event)
Definition: Case.java:653
static final String REPORTS_FOLDER
Definition: Case.java:181
static final String TEMP_FOLDER
Definition: Case.java:175
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:701
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2649
void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event)
Definition: Case.java:658

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.