Autopsy  4.18.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Case.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-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;
141 import org.sleuthkit.datamodel.Blackboard;
142 import org.sleuthkit.datamodel.BlackboardArtifact;
143 import org.sleuthkit.datamodel.BlackboardArtifactTag;
144 import org.sleuthkit.datamodel.CaseDbConnectionInfo;
145 import org.sleuthkit.datamodel.Content;
146 import org.sleuthkit.datamodel.ContentTag;
147 import org.sleuthkit.datamodel.DataSource;
148 import org.sleuthkit.datamodel.FileSystem;
149 import org.sleuthkit.datamodel.Host;
150 import org.sleuthkit.datamodel.Image;
151 import org.sleuthkit.datamodel.OsAccount;
152 import org.sleuthkit.datamodel.Person;
153 import org.sleuthkit.datamodel.Report;
154 import org.sleuthkit.datamodel.SleuthkitCase;
155 import org.sleuthkit.datamodel.TimelineManager;
156 import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
157 import org.sleuthkit.datamodel.TskCoreException;
158 import org.sleuthkit.datamodel.TskDataException;
159 import org.sleuthkit.datamodel.TskEvent;
160 import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
161 
165 public class Case {
166 
167  private static final int CASE_LOCK_TIMEOUT_MINS = 1;
168  private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
169  private static final String APP_NAME = UserPreferences.getAppName();
170  private static final String TEMP_FOLDER = "Temp";
171  private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
172  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
173  private static final String CACHE_FOLDER = "Cache"; //NON-NLS
174  private static final String EXPORT_FOLDER = "Export"; //NON-NLS
175  private static final String LOG_FOLDER = "Log"; //NON-NLS
176  private static final String REPORTS_FOLDER = "Reports"; //NON-NLS
177  private static final String CONFIG_FOLDER = "Config"; // NON-NLS
178  private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
179  private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
180  private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
181  private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
182  private static final Logger logger = Logger.getLogger(Case.class.getName());
184  private static final Object caseActionSerializationLock = new Object();
185  private static Future<?> backgroundOpenFileSystemsFuture = null;
186  private static final ExecutorService openFileSystemsExecutor
187  = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
188  private static volatile Frame mainFrame;
189  private static volatile Case currentCase;
190  private final CaseMetadata metadata;
191  private volatile ExecutorService caseActionExecutor;
193  private SleuthkitCase caseDb;
195  private CollaborationMonitor collaborationMonitor;
197 
198  private volatile boolean hasDataSource = false;
199  private volatile boolean hasData = false;
200 
201  /*
202  * Get a reference to the main window of the desktop application to use to
203  * parent pop up dialogs and initialize the application name for use in
204  * changing the main window title.
205  */
206  static {
207  WindowManager.getDefault().invokeWhenUIReady(() -> {
208  mainFrame = WindowManager.getDefault().getMainWindow();
209  });
210  }
211 
215  public enum CaseType {
216 
217  SINGLE_USER_CASE("Single-user case"), //NON-NLS
218  MULTI_USER_CASE("Multi-user case"); //NON-NLS
219 
220  private final String typeName;
221 
229  public static CaseType fromString(String typeName) {
230  if (typeName != null) {
231  for (CaseType c : CaseType.values()) {
232  if (typeName.equalsIgnoreCase(c.toString())) {
233  return c;
234  }
235  }
236  }
237  return null;
238  }
239 
245  @Override
246  public String toString() {
247  return typeName;
248  }
249 
255  @Messages({
256  "Case_caseType_singleUser=Single-user case",
257  "Case_caseType_multiUser=Multi-user case"
258  })
260  if (fromString(typeName) == SINGLE_USER_CASE) {
261  return Bundle.Case_caseType_singleUser();
262  } else {
263  return Bundle.Case_caseType_multiUser();
264  }
265  }
266 
272  private CaseType(String typeName) {
273  this.typeName = typeName;
274  }
275 
286  @Deprecated
287  public boolean equalsName(String otherTypeName) {
288  return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
289  }
290 
291  };
292 
297  public enum Events {
298 
306  @Deprecated
315  @Deprecated
324  @Deprecated
435  /*
436  * An item in the central repository has had its comment modified. The
437  * old value is null, the new value is string for current comment.
438  */
488 
489  };
490 
496  private final class SleuthkitEventListener {
497 
498  @Subscribe
499  public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
500  eventPublisher.publish(new TimelineEventAddedEvent(event));
501  }
502 
503  @SuppressWarnings("deprecation")
504  @Subscribe
505  public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) {
506  for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) {
507  /*
508  * IngestServices.fireModuleDataEvent is deprecated to
509  * discourage ingest module writers from using it (they should
510  * use org.sleuthkit.datamodel.Blackboard.postArtifact(s)
511  * instead), but a way to publish
512  * Blackboard.ArtifactsPostedEvents from the SleuthKit layer as
513  * Autopsy ModuleDataEvents is still needed.
514  */
516  event.getModuleName(),
517  artifactType,
518  event.getArtifacts(artifactType)));
519  }
520  }
521 
522  @Subscribe
523  public void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event) {
524  hasData = true;
525  eventPublisher.publish(new OsAccountsAddedEvent(event.getOsAcounts()));
526  }
527 
528  @Subscribe
529  public void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event) {
530  eventPublisher.publish(new OsAccountsUpdatedEvent(event.getOsAcounts()));
531  }
532 
533  @Subscribe
534  public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) {
535  try {
536  hasData = dbHasData();
537  } catch (TskCoreException ex) {
538  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
539  }
540  eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds()));
541  }
542 
543  @Subscribe
544  public void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event) {
545  eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances()));
546  }
547 
554  @Subscribe
555  public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) {
556  hasData = true;
557  eventPublisher.publish(new HostsAddedEvent(event.getHosts()));
558  }
559 
566  @Subscribe
567  public void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event) {
568  eventPublisher.publish(new HostsUpdatedEvent(event.getHosts()));
569  }
570 
577  @Subscribe
578  public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) {
579  try {
580  hasData = dbHasData();
581  } catch (TskCoreException ex) {
582  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
583  }
584 
585  eventPublisher.publish(new HostsDeletedEvent(event.getHostIds()));
586  }
587 
594  @Subscribe
595  public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) {
596  eventPublisher.publish(new PersonsAddedEvent(event.getPersons()));
597  }
598 
605  @Subscribe
606  public void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event) {
607  eventPublisher.publish(new PersonsUpdatedEvent(event.getPersons()));
608  }
609 
616  @Subscribe
617  public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) {
618  eventPublisher.publish(new PersonsDeletedEvent(event.getPersonIds()));
619  }
620 
621  @Subscribe
622  public void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event) {
623  eventPublisher.publish(new HostsAddedToPersonEvent(event.getPerson(), event.getHosts()));
624  }
625 
626  @Subscribe
627  public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) {
628  eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds()));
629  }
630 
631  }
632 
639  public static void addPropertyChangeListener(PropertyChangeListener listener) {
640  addEventSubscriber(Stream.of(Events.values())
641  .map(Events::toString)
642  .collect(Collectors.toSet()), listener);
643  }
644 
651  public static void removePropertyChangeListener(PropertyChangeListener listener) {
652  removeEventSubscriber(Stream.of(Events.values())
653  .map(Events::toString)
654  .collect(Collectors.toSet()), listener);
655  }
656 
665  @Deprecated
666  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
667  eventPublisher.addSubscriber(eventNames, subscriber);
668  }
669 
676  public static void addEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
677  eventTypes.forEach((Events event) -> {
678  eventPublisher.addSubscriber(event.toString(), subscriber);
679  });
680  }
681 
690  @Deprecated
691  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
692  eventPublisher.addSubscriber(eventName, subscriber);
693  }
694 
701  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
702  eventPublisher.removeSubscriber(eventName, subscriber);
703  }
704 
711  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
712  eventPublisher.removeSubscriber(eventNames, subscriber);
713  }
714 
721  public static void removeEventTypeSubscriber(Set<Events> eventTypes, PropertyChangeListener subscriber) {
722  eventTypes.forEach((Events event) -> {
723  eventPublisher.removeSubscriber(event.toString(), subscriber);
724  });
725  }
726 
735  public static boolean isValidName(String caseName) {
736  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
737  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
738  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
739  }
740 
765  @Deprecated
766  public static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseActionCancelledException {
767  createAsCurrentCase(caseType, caseDir, new CaseDetails(caseDisplayName, caseNumber, examiner, "", "", ""));
768  }
769 
789  @Messages({
790  "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
791  "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
792  })
793  public static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails) throws CaseActionException, CaseActionCancelledException {
794  if (caseDetails.getCaseDisplayName().isEmpty()) {
795  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseName());
796  }
797  if (caseDir.isEmpty()) {
798  throw new CaseActionException(Bundle.Case_exceptionMessage_emptyCaseDir());
799  }
800  openAsCurrentCase(new Case(caseType, caseDir, caseDetails), true);
801  }
802 
816  @Messages({
817  "# {0} - exception message", "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.",
818  "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
819  })
820  public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
822  try {
823  metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
824  } catch (CaseMetadataException ex) {
825  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToReadMetadata(ex.getLocalizedMessage()), ex);
826  }
828  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
829  }
830  openAsCurrentCase(new Case(metadata), false);
831  }
832 
838  public static boolean isCaseOpen() {
839  return currentCase != null;
840  }
841 
849  public static Case getCurrentCase() {
850  try {
851  return getCurrentCaseThrows();
852  } catch (NoCurrentCaseException ex) {
853  /*
854  * Throw a runtime exception, since this is a programming error.
855  */
856  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex);
857  }
858  }
859 
875  /*
876  * TODO (JIRA-3825): Introduce a reference counting scheme for this get
877  * case method.
878  */
879  Case openCase = currentCase;
880  if (openCase == null) {
881  throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
882  } else {
883  return openCase;
884  }
885  }
886 
895  @Messages({
896  "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
897  "Case.progressIndicatorTitle.closingCase=Closing Case"
898  })
899  public static void closeCurrentCase() throws CaseActionException {
900  synchronized (caseActionSerializationLock) {
901  if (null == currentCase) {
902  return;
903  }
904  Case closedCase = currentCase;
905  try {
906  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
907  logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
908  closedCase.doCloseCaseAction();
909  logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
910  } catch (CaseActionException ex) {
911  logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
912  throw ex;
913  } finally {
914  currentCase = null;
917  }
918  }
919  }
920  }
921 
930  public static void deleteCurrentCase() throws CaseActionException {
931  synchronized (caseActionSerializationLock) {
932  if (null == currentCase) {
933  return;
934  }
935  CaseMetadata metadata = currentCase.getMetadata();
937  deleteCase(metadata);
938  }
939  }
940 
951  @Messages({
952  "Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
953  })
954  static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
955  synchronized (caseActionSerializationLock) {
956  if (null == currentCase) {
957  return;
958  }
959 
960  /*
961  * Close the current case to release the shared case lock.
962  */
963  CaseMetadata caseMetadata = currentCase.getMetadata();
965 
966  /*
967  * Re-open the case with an exclusive case lock, delete the data
968  * source, and close the case again, releasing the exclusive case
969  * lock.
970  */
971  Case theCase = new Case(caseMetadata);
972  theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
973 
974  /*
975  * Re-open the case with a shared case lock.
976  */
977  openAsCurrentCase(new Case(caseMetadata), false);
978  }
979  }
980 
992  @Messages({
993  "Case.progressIndicatorTitle.deletingCase=Deleting Case",
994  "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
995  "# {0} - case display name", "Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled."
996  })
997  public static void deleteCase(CaseMetadata metadata) throws CaseActionException {
998  synchronized (caseActionSerializationLock) {
999  if (null != currentCase) {
1000  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase());
1001  }
1002  }
1003 
1004  ProgressIndicator progressIndicator;
1006  progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingCase());
1007  } else {
1008  progressIndicator = new LoggingProgressIndicator();
1009  }
1010  progressIndicator.start(Bundle.Case_progressMessage_preparing());
1011  try {
1012  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
1013  deleteSingleUserCase(metadata, progressIndicator);
1014  } else {
1015  try {
1016  deleteMultiUserCase(metadata, progressIndicator);
1017  } catch (InterruptedException ex) {
1018  /*
1019  * Note that task cancellation is not currently supported
1020  * for this code path, so this catch block is not expected
1021  * to be executed.
1022  */
1023  throw new CaseActionException(Bundle.Case_exceptionMessage_deletionInterrupted(metadata.getCaseDisplayName()), ex);
1024  }
1025  }
1026  } finally {
1027  progressIndicator.finish();
1028  }
1029  }
1030 
1041  @Messages({
1042  "Case.progressIndicatorTitle.creatingCase=Creating Case",
1043  "Case.progressIndicatorTitle.openingCase=Opening Case",
1044  "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
1045  })
1046  private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
1047  synchronized (caseActionSerializationLock) {
1048  if (null != currentCase) {
1049  try {
1050  closeCurrentCase();
1051  } catch (CaseActionException ex) {
1052  /*
1053  * Notify the user and continue (the error has already been
1054  * logged in closeCurrentCase.
1055  */
1056  MessageNotifyUtil.Message.error(ex.getLocalizedMessage());
1057  }
1058  }
1059  try {
1060  logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1061  String progressIndicatorTitle;
1063  if (isNewCase) {
1064  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
1065  openCaseAction = newCurrentCase::create;
1066  } else {
1067  progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
1068  openCaseAction = newCurrentCase::open;
1069  }
1070  newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
1071  currentCase = newCurrentCase;
1072  logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
1074  updateGUIForCaseOpened(newCurrentCase);
1075  }
1076  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
1077  } catch (CaseActionCancelledException ex) {
1078  logger.log(Level.INFO, String.format("Cancelled opening %s (%s) in %s as the current case", newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory())); //NON-NLS
1079  throw ex;
1080  } catch (CaseActionException ex) {
1081  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
1082  throw ex;
1083  }
1084  }
1085  }
1086 
1095  private static String displayNameToUniqueName(String caseDisplayName) {
1096  /*
1097  * Replace all non-ASCII characters.
1098  */
1099  String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
1100 
1101  /*
1102  * Replace all control characters.
1103  */
1104  uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
1105 
1106  /*
1107  * Replace /, \, :, ?, space, ' ".
1108  */
1109  uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
1110 
1111  /*
1112  * Make it all lowercase.
1113  */
1114  uniqueCaseName = uniqueCaseName.toLowerCase();
1115 
1116  /*
1117  * Add a time stamp for uniqueness.
1118  */
1119  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
1120  Date date = new Date();
1121  uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
1122 
1123  return uniqueCaseName;
1124  }
1125 
1135  public static void createCaseDirectory(String caseDirPath, CaseType caseType) throws CaseActionException {
1136  /*
1137  * Check the case directory path and permissions. The case directory may
1138  * already exist.
1139  */
1140  File caseDir = new File(caseDirPath);
1141  if (caseDir.exists()) {
1142  if (caseDir.isFile()) {
1143  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDirPath));
1144  } else if (!caseDir.canRead() || !caseDir.canWrite()) {
1145  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDirPath));
1146  }
1147  }
1148 
1149  /*
1150  * Create the case directory, if it does not already exist.
1151  */
1152  if (!caseDir.mkdirs()) {
1153  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDirPath));
1154  }
1155 
1156  /*
1157  * Create the subdirectories of the case directory, if they do not
1158  * already exist. Note that multi-user cases get an extra layer of
1159  * subdirectories, one subdirectory per application host machine.
1160  */
1161  String hostPathComponent = "";
1162  if (caseType == CaseType.MULTI_USER_CASE) {
1163  hostPathComponent = File.separator + NetworkUtils.getLocalHostName();
1164  }
1165 
1166  Path exportDir = Paths.get(caseDirPath, hostPathComponent, EXPORT_FOLDER);
1167  if (!exportDir.toFile().mkdirs()) {
1168  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", exportDir));
1169  }
1170 
1171  Path logsDir = Paths.get(caseDirPath, hostPathComponent, LOG_FOLDER);
1172  if (!logsDir.toFile().mkdirs()) {
1173  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", logsDir));
1174  }
1175 
1176  Path cacheDir = Paths.get(caseDirPath, hostPathComponent, CACHE_FOLDER);
1177  if (!cacheDir.toFile().mkdirs()) {
1178  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", cacheDir));
1179  }
1180 
1181  Path moduleOutputDir = Paths.get(caseDirPath, hostPathComponent, MODULE_FOLDER);
1182  if (!moduleOutputDir.toFile().mkdirs()) {
1183  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir", moduleOutputDir));
1184  }
1185 
1186  Path reportsDir = Paths.get(caseDirPath, hostPathComponent, REPORTS_FOLDER);
1187  if (!reportsDir.toFile().mkdirs()) {
1188  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir", reportsDir));
1189  }
1190  }
1191 
1199  static Map<Long, String> getImagePaths(SleuthkitCase db) {
1200  Map<Long, String> imgPaths = new HashMap<>();
1201  try {
1202  Map<Long, List<String>> imgPathsList = db.getImagePaths();
1203  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
1204  if (entry.getValue().size() > 0) {
1205  imgPaths.put(entry.getKey(), entry.getValue().get(0));
1206  }
1207  }
1208  } catch (TskCoreException ex) {
1209  logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
1210  }
1211  return imgPaths;
1212  }
1213 
1224  @Messages({
1225  "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
1226  })
1227  private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
1228  try {
1229  Path caseDirPath = Paths.get(caseDir);
1230  String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
1231  Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
1232  return lock;
1233  } catch (InterruptedException ex) {
1234  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
1235  } catch (CoordinationServiceException ex) {
1236  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
1237  }
1238  }
1239 
1240  private static String getNameForTitle() {
1241  //Method should become unnecessary once technical debt story 3334 is done.
1242  if (UserPreferences.getAppName().equals(Version.getName())) {
1243  //Available version number is version number for this application
1244  return String.format("%s %s", UserPreferences.getAppName(), Version.getVersion());
1245  } else {
1246  return UserPreferences.getAppName();
1247  }
1248  }
1249 
1253  private static void updateGUIForCaseOpened(Case newCurrentCase) {
1254  /*
1255  * If the case database was upgraded for a new schema and a backup
1256  * database was created, notify the user.
1257  */
1258  SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase();
1259  String backupDbPath = caseDb.getBackupDatabasePath();
1260  if (null != backupDbPath) {
1261  JOptionPane.showMessageDialog(
1262  mainFrame,
1263  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath),
1264  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
1265  JOptionPane.INFORMATION_MESSAGE);
1266  }
1267 
1268  /*
1269  * Look for the files for the data sources listed in the case database
1270  * and give the user the opportunity to locate any that are missing.
1271  */
1272  Map<Long, String> imgPaths = getImagePaths(caseDb);
1273  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
1274  long obj_id = entry.getKey();
1275  String path = entry.getValue();
1276  boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
1277  if (!fileExists) {
1278  try {
1279  // Using invokeAndWait means that the dialog will
1280  // open on the EDT but this thread will wait for an
1281  // answer. Using invokeLater would cause this loop to
1282  // end before all of the dialogs appeared.
1283  SwingUtilities.invokeAndWait(new Runnable() {
1284  @Override
1285  public void run() {
1286  int response = JOptionPane.showConfirmDialog(
1287  mainFrame,
1288  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
1289  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
1290  JOptionPane.YES_NO_OPTION);
1291  if (response == JOptionPane.YES_OPTION) {
1292  MissingImageDialog.makeDialog(obj_id, caseDb);
1293  } else {
1294  logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
1295 
1296  }
1297  }
1298 
1299  });
1300  } catch (InterruptedException | InvocationTargetException ex) {
1301  logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
1302  }
1303  }
1304  }
1305 
1306  /*
1307  * Enable the case-specific actions.
1308  */
1309  CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
1310  CallableSystemAction.get(OpenHostsAction.class).setEnabled(true);
1311  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1312  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
1313  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
1314  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
1315  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
1316  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
1317  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
1318  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1319  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true);
1320 
1321  /*
1322  * Add the case to the recent cases tracker that supplies a list of
1323  * recent cases to the recent cases menu item and the open/create case
1324  * dialog.
1325  */
1326  RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
1327  final boolean hasData = newCurrentCase.hasData();
1328 
1329  SwingUtilities.invokeLater(() -> {
1330  /*
1331  * Open the top components (windows within the main application
1332  * window).
1333  *
1334  * Note: If the core windows are not opened here, they will be
1335  * opened via the DirectoryTreeTopComponent 'propertyChange()'
1336  * method on a DATA_SOURCE_ADDED event.
1337  */
1338  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1339  if (hasData) {
1341  } else {
1342  //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible.
1344  }
1345  mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
1346 
1347  /*
1348  * Reset the main window title to:
1349  *
1350  * [curent case display name] - [application name].
1351  */
1352  mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle());
1353  });
1354  }
1355 
1356  /*
1357  * Update the GUI to to reflect the lack of a current case.
1358  */
1359  private static void updateGUIForCaseClosed() {
1361  SwingUtilities.invokeLater(() -> {
1362  /*
1363  * Close the top components (windows within the main application
1364  * window).
1365  */
1367 
1368  /*
1369  * Disable the case-specific menu items.
1370  */
1371  CallableSystemAction.get(AddImageAction.class).setEnabled(false);
1372  CallableSystemAction.get(OpenHostsAction.class).setEnabled(false);
1373  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
1374  CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false);
1375  CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
1376  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
1377  CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
1378  CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
1379  CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
1380  CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
1381  CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(false);
1382 
1383  /*
1384  * Clear the notifications in the notfier component in the lower
1385  * right hand corner of the main application window.
1386  */
1388 
1389  /*
1390  * Reset the main window title to be just the application name,
1391  * instead of [curent case display name] - [application name].
1392  */
1393  mainFrame.setTitle(getNameForTitle());
1394  });
1395  }
1396  }
1397 
1403  public SleuthkitCase getSleuthkitCase() {
1404  return this.caseDb;
1405  }
1406 
1413  return caseServices;
1414  }
1415 
1422  return metadata.getCaseType();
1423  }
1424 
1430  public String getCreatedDate() {
1431  return metadata.getCreatedDate();
1432  }
1433 
1439  public String getName() {
1440  return metadata.getCaseName();
1441  }
1442 
1448  public String getDisplayName() {
1449  return metadata.getCaseDisplayName();
1450  }
1451 
1457  public String getNumber() {
1458  return metadata.getCaseNumber();
1459  }
1460 
1466  public String getExaminer() {
1467  return metadata.getExaminer();
1468  }
1469 
1475  public String getExaminerPhone() {
1476  return metadata.getExaminerPhone();
1477  }
1478 
1484  public String getExaminerEmail() {
1485  return metadata.getExaminerEmail();
1486  }
1487 
1493  public String getCaseNotes() {
1494  return metadata.getCaseNotes();
1495  }
1496 
1502  public String getCaseDirectory() {
1503  return metadata.getCaseDirectory();
1504  }
1505 
1514  public String getOutputDirectory() {
1515  String caseDirectory = getCaseDirectory();
1516  Path hostPath;
1517  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
1518  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1519  } else {
1520  hostPath = Paths.get(caseDirectory);
1521  }
1522  if (!hostPath.toFile().exists()) {
1523  hostPath.toFile().mkdirs();
1524  }
1525  return hostPath.toString();
1526  }
1527 
1531  private Path getBaseSystemTempPath() {
1532  return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName());
1533  }
1534 
1541  public String getTempDirectory() {
1542  // NOTE: UserPreferences may also be affected by changes in this method.
1543  // See JIRA-7505 for more information.
1544  Path basePath = null;
1545  // get base temp path for the case based on user preference
1547  case CUSTOM:
1548  String customDirectory = UserMachinePreferences.getCustomTempDirectory();
1549  basePath = (StringUtils.isBlank(customDirectory))
1550  ? null
1551  : Paths.get(customDirectory, APP_NAME, getName());
1552  break;
1553  case CASE:
1554  basePath = Paths.get(getCaseDirectory());
1555  break;
1556  case SYSTEM:
1557  default:
1558  // at this level, if the case directory is specified for a temp
1559  // directory, return the system temp directory instead.
1560  basePath = getBaseSystemTempPath();
1561  break;
1562  }
1563 
1564  basePath = basePath == null ? getBaseSystemTempPath() : basePath;
1565 
1566  // get sub directories based on multi user vs. single user
1567  Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType()))
1568  ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER)
1569  : Paths.get(TEMP_FOLDER);
1570 
1571  File caseTempDir = basePath
1572  .resolve(caseRelPath)
1573  .toFile();
1574 
1575  // ensure directory exists
1576  if (!caseTempDir.exists()) {
1577  caseTempDir.mkdirs();
1578  }
1579 
1580  return caseTempDir.getAbsolutePath();
1581  }
1582 
1589  public String getCacheDirectory() {
1590  return getOrCreateSubdirectory(CACHE_FOLDER);
1591  }
1592 
1599  public String getExportDirectory() {
1600  return getOrCreateSubdirectory(EXPORT_FOLDER);
1601  }
1602 
1609  public String getLogDirectoryPath() {
1610  return getOrCreateSubdirectory(LOG_FOLDER);
1611  }
1612 
1619  public String getReportDirectory() {
1620  return getOrCreateSubdirectory(REPORTS_FOLDER);
1621  }
1622 
1629  public String getConfigDirectory() {
1630  return getOrCreateSubdirectory(CONFIG_FOLDER);
1631  }
1632 
1639  public String getModuleDirectory() {
1640  return getOrCreateSubdirectory(MODULE_FOLDER);
1641  }
1642 
1651  Path path = Paths.get(getModuleDirectory());
1653  return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1654  } else {
1655  return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1656  }
1657  }
1658 
1668  public List<Content> getDataSources() throws TskCoreException {
1669  return caseDb.getRootObjects();
1670  }
1671 
1677  public Set<TimeZone> getTimeZones() {
1678  Set<TimeZone> timezones = new HashSet<>();
1679  String query = "SELECT time_zone FROM data_source_info";
1680  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
1681  ResultSet timeZoneSet = dbQuery.getResultSet();
1682  while (timeZoneSet.next()) {
1683  String timeZone = timeZoneSet.getString("time_zone");
1684  if (timeZone != null && !timeZone.isEmpty()) {
1685  timezones.add(TimeZone.getTimeZone(timeZone));
1686  }
1687  }
1688  } catch (TskCoreException | SQLException ex) {
1689  logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1690  }
1691  return timezones;
1692  }
1693 
1700  public String getTextIndexName() {
1701  return getMetadata().getTextIndexName();
1702  }
1703 
1709  public boolean hasData() {
1710  return hasData;
1711  }
1712 
1718  public boolean hasDataSource() {
1719  return hasDataSource;
1720  }
1721 
1732  public void notifyAddingDataSource(UUID eventId) {
1733  hasDataSource = true;
1734  hasData = true;
1735  eventPublisher.publish(new AddingDataSourceEvent(eventId));
1736  }
1737 
1748  public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) {
1749  eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId));
1750  }
1751 
1763  public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) {
1764  eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId));
1765  }
1766 
1776  public void notifyDataSourceNameChanged(Content dataSource, String newName) {
1777  eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
1778  }
1779 
1787  public void notifyContentTagAdded(ContentTag newTag) {
1788  notifyContentTagAdded(newTag, null);
1789  }
1790 
1800  public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
1801  eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
1802  }
1803 
1811  public void notifyContentTagDeleted(ContentTag deletedTag) {
1812  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
1813  }
1814 
1822  public void notifyTagDefinitionChanged(String changedTagName) {
1823  //leaving new value of changedTagName as null, because we do not currently support changing the display name of a tag.
1824  eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
1825  }
1826 
1837  public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
1838  try {
1839  eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
1840  } catch (NoCurrentCaseException ex) {
1841  logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
1842  }
1843  }
1844 
1852  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
1853  notifyBlackBoardArtifactTagAdded(newTag, null);
1854  }
1855 
1865  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
1866  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
1867  }
1868 
1876  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
1877  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
1878  }
1879 
1891  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1892  addReport(localPath, srcModuleName, reportName, null);
1893  }
1894 
1909  public Report addReport(String localPath, String srcModuleName, String reportName, Content parent) throws TskCoreException {
1910  String normalizedLocalPath;
1911  try {
1912  if (localPath.toLowerCase().contains("http:")) {
1913  normalizedLocalPath = localPath;
1914  } else {
1915  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1916  }
1917  } catch (InvalidPathException ex) {
1918  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1919  throw new TskCoreException(errorMsg, ex);
1920  }
1921  hasData = true;
1922 
1923  Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent);
1924  eventPublisher.publish(new ReportAddedEvent(report));
1925  return report;
1926  }
1927 
1936  public List<Report> getAllReports() throws TskCoreException {
1937  return this.caseDb.getAllReports();
1938  }
1939 
1948  public void deleteReports(Collection<? extends Report> reports) throws TskCoreException {
1949  for (Report report : reports) {
1950  this.caseDb.deleteReport(report);
1951  }
1952 
1953  try {
1954  hasData = dbHasData();
1955  } catch (TskCoreException ex) {
1956  logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex);
1957  }
1958 
1959  for (Report report : reports) {
1960  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null));
1961  }
1962  }
1963 
1970  return metadata;
1971  }
1972 
1980  @Messages({
1981  "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata"
1982  })
1983  void updateCaseDetails(CaseDetails caseDetails) throws CaseActionException {
1984  CaseDetails oldCaseDetails = metadata.getCaseDetails();
1985  try {
1986  metadata.setCaseDetails(caseDetails);
1987  } catch (CaseMetadataException ex) {
1988  throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError(), ex);
1989  }
1990  if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
1991  try {
1992  CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
1993  nodeData.setDisplayName(caseDetails.getCaseDisplayName());
1994  CaseNodeData.writeCaseNodeData(nodeData);
1995  } catch (CaseNodeDataException | InterruptedException ex) {
1996  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
1997  }
1998  }
1999  if (!oldCaseDetails.getCaseNumber().equals(caseDetails.getCaseNumber())) {
2000  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getCaseNumber(), caseDetails.getCaseNumber()));
2001  }
2002  if (!oldCaseDetails.getExaminerName().equals(caseDetails.getExaminerName())) {
2003  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseDetails.getExaminerName(), caseDetails.getExaminerName()));
2004  }
2005  if (!oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
2006  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseDetails.getCaseDisplayName(), caseDetails.getCaseDisplayName()));
2007  }
2008  eventPublisher.publish(new AutopsyEvent(Events.CASE_DETAILS.toString(), oldCaseDetails, caseDetails));
2009  if (RuntimeProperties.runningWithGUI()) {
2010  SwingUtilities.invokeLater(() -> {
2011  mainFrame.setTitle(caseDetails.getCaseDisplayName() + " - " + getNameForTitle());
2012  try {
2013  RecentCases.getInstance().updateRecentCase(oldCaseDetails.getCaseDisplayName(), metadata.getFilePath().toString(), caseDetails.getCaseDisplayName(), metadata.getFilePath().toString());
2014  } catch (Exception ex) {
2015  logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
2016  }
2017  });
2018  }
2019  }
2020 
2033  private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
2034  this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
2035  }
2036 
2042  private Case(CaseMetadata caseMetaData) {
2043  metadata = caseMetaData;
2044  sleuthkitEventListener = new SleuthkitEventListener();
2045  }
2046 
2076  @Messages({
2077  "Case.progressIndicatorCancelButton.label=Cancel",
2078  "Case.progressMessage.preparing=Preparing...",
2079  "Case.progressMessage.cancelling=Cancelling...",
2080  "Case.exceptionMessage.cancelled=Cancelled.",
2081  "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
2082  })
2083  private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
2084  /*
2085  * Create and start either a GUI progress indicator (with or without a
2086  * cancel button) or a logging progress indicator.
2087  */
2088  CancelButtonListener cancelButtonListener = null;
2089  ProgressIndicator progressIndicator;
2091  if (allowCancellation) {
2092  cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
2093  progressIndicator = new ModalDialogProgressIndicator(
2094  mainFrame,
2095  progressIndicatorTitle,
2096  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2097  Bundle.Case_progressIndicatorCancelButton_label(),
2098  cancelButtonListener);
2099  } else {
2100  progressIndicator = new ModalDialogProgressIndicator(
2101  mainFrame,
2102  progressIndicatorTitle);
2103  }
2104  } else {
2105  progressIndicator = new LoggingProgressIndicator();
2106  }
2107  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2108 
2109  /*
2110  * Do the case action in the single thread in the case action executor.
2111  * If the case is a multi-user case, a case lock is acquired and held
2112  * until explictly released and an exclusive case resources lock is
2113  * aquired and held for the duration of the action.
2114  */
2115  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
2116  caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
2117  Future<Void> future = caseActionExecutor.submit(() -> {
2118  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2119  caseAction.execute(progressIndicator, additionalParams);
2120  } else {
2121  acquireCaseLock(caseLockType);
2122  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2123  if (null == resourcesLock) {
2124  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2125  }
2126  caseAction.execute(progressIndicator, additionalParams);
2127  } catch (CaseActionException ex) {
2128  releaseCaseLock();
2129  throw ex;
2130  }
2131  }
2132  return null;
2133  });
2134  if (null != cancelButtonListener) {
2135  cancelButtonListener.setCaseActionFuture(future);
2136  }
2137 
2138  /*
2139  * Wait for the case action task to finish.
2140  */
2141  try {
2142  future.get();
2143  } catch (InterruptedException discarded) {
2144  /*
2145  * The thread this method is running in has been interrupted.
2146  */
2147  if (null != cancelButtonListener) {
2148  cancelButtonListener.actionPerformed(null);
2149  } else {
2150  future.cancel(true);
2151  }
2152  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2153  } catch (CancellationException discarded) {
2154  /*
2155  * The case action has been cancelled.
2156  */
2157  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2158  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2159  } catch (ExecutionException ex) {
2160  /*
2161  * The case action has thrown an exception.
2162  */
2163  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2164  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
2165  } finally {
2166  progressIndicator.finish();
2167  }
2168  }
2169 
2185  private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2186  assert (additionalParams == null);
2187  try {
2189  createCaseDirectoryIfDoesNotExist(progressIndicator);
2191  switchLoggingToCaseLogsDirectory(progressIndicator);
2193  saveCaseMetadataToFile(progressIndicator);
2195  createCaseNodeData(progressIndicator);
2198  createCaseDatabase(progressIndicator);
2200  openCaseLevelServices(progressIndicator);
2202  openAppServiceCaseResources(progressIndicator, true);
2204  openCommunicationChannels(progressIndicator);
2205  return null;
2206 
2207  } catch (CaseActionException ex) {
2208  /*
2209  * Cancellation or failure. The sleep is a little hack to clear the
2210  * interrupted flag for this thread if this is a cancellation
2211  * scenario, so that the clean up can run to completion in the
2212  * current thread.
2213  */
2214  try {
2215  Thread.sleep(1);
2216  } catch (InterruptedException discarded) {
2217  }
2218  close(progressIndicator);
2219  throw ex;
2220  }
2221  }
2222 
2237  private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2238  assert (additionalParams == null);
2239  try {
2241  switchLoggingToCaseLogsDirectory(progressIndicator);
2243  updateCaseNodeData(progressIndicator);
2245  deleteTempfilesFromCaseDirectory(progressIndicator);
2247  openCaseDataBase(progressIndicator);
2249  openCaseLevelServices(progressIndicator);
2251  openAppServiceCaseResources(progressIndicator, false);
2253  openCommunicationChannels(progressIndicator);
2256  return null;
2257 
2258  } catch (CaseActionException ex) {
2259  /*
2260  * Cancellation or failure. The sleep is a little hack to clear the
2261  * interrupted flag for this thread if this is a cancellation
2262  * scenario, so that the clean up can run to completion in the
2263  * current thread.
2264  */
2265  try {
2266  Thread.sleep(1);
2267  } catch (InterruptedException discarded) {
2268  }
2269  close(progressIndicator);
2270  throw ex;
2271  }
2272  }
2273 
2283  @Messages({
2284  "# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
2285  "# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
2286  })
2288  if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
2289  backgroundOpenFileSystemsFuture.cancel(true);
2290  }
2291 
2293  backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
2294  }
2295 
2300  private static class BackgroundOpenFileSystemsTask implements Runnable {
2301 
2302  private final SleuthkitCase tskCase;
2303  private final String caseName;
2304  private final long MAX_IMAGE_THRESHOLD = 100;
2306 
2315  BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
2316  this.tskCase = tskCase;
2317  this.progressIndicator = progressIndicator;
2318  caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
2319  }
2320 
2328  private void checkIfCancelled() throws InterruptedException {
2329  if (Thread.interrupted()) {
2330  throw new InterruptedException();
2331  }
2332  }
2333 
2339  private List<Image> getImages() {
2340  progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
2341  try {
2342  return this.tskCase.getImages();
2343  } catch (TskCoreException ex) {
2344  logger.log(
2345  Level.SEVERE,
2346  String.format("Could not obtain images while opening case: %s.", caseName),
2347  ex);
2348 
2349  return null;
2350  }
2351  }
2352 
2362  private void openFileSystems(List<Image> images) throws TskCoreException, InterruptedException {
2363  byte[] tempBuff = new byte[512];
2364 
2365  for (Image image : images) {
2366  String imageStr = image.getName();
2367 
2368  progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
2369 
2370  Collection<FileSystem> fileSystems = this.tskCase.getImageFileSystems(image);
2371  checkIfCancelled();
2372  for (FileSystem fileSystem : fileSystems) {
2373  fileSystem.read(tempBuff, 0, 512);
2374  checkIfCancelled();
2375  }
2376 
2377  }
2378  }
2379 
2380  @Override
2381  public void run() {
2382  try {
2383  checkIfCancelled();
2384  List<Image> images = getImages();
2385  if (images == null) {
2386  return;
2387  }
2388 
2389  if (images.size() > MAX_IMAGE_THRESHOLD) {
2390  // If we have a large number of images, don't try to preload anything
2391  logger.log(
2392  Level.INFO,
2393  String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size()));
2394  return;
2395  }
2396 
2397  checkIfCancelled();
2398  openFileSystems(images);
2399  } catch (InterruptedException ex) {
2400  logger.log(
2401  Level.INFO,
2402  String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
2403  } catch (Exception ex) {
2404  // Exception firewall
2405  logger.log(Level.WARNING, "Error while opening file systems in background", ex);
2406  }
2407  }
2408 
2409  }
2410 
2425  @Messages({
2426  "Case.progressMessage.deletingDataSource=Removing the data source from the case...",
2427  "Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
2428  "Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
2429  "Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
2430  Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
2431  assert (additionalParams instanceof Long);
2432  open(progressIndicator, null);
2433  try {
2434  progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
2435  Long dataSourceObjectID = (Long) additionalParams;
2436  try {
2437  DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
2438  if (dataSource == null) {
2439  throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
2440  }
2441  SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
2442  } catch (TskDataException | TskCoreException ex) {
2443  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
2444  }
2445  try {
2446  this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
2447  } catch (KeywordSearchServiceException ex) {
2448  throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
2449  }
2450  eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
2451  return null;
2452  } finally {
2453  close(progressIndicator);
2454  releaseCaseLock();
2455  }
2456  }
2457 
2468  public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
2469 
2470  if (portableCaseFolder.exists()) {
2471  throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
2472  }
2473  if (!portableCaseFolder.mkdirs()) {
2474  throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
2475  }
2476 
2477  CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
2479  try {
2480  CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
2481  caseName, details, metadata);
2482  portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2483  } catch (CaseMetadataException ex) {
2484  throw new TskCoreException("Error creating case metadata", ex);
2485  }
2486 
2487  // Create the Sleuthkit case
2488  SleuthkitCase portableSleuthkitCase;
2489  String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
2490  portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
2491 
2492  return portableSleuthkitCase;
2493  }
2494 
2505  if (Thread.currentThread().isInterrupted()) {
2506  throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
2507  }
2508  }
2509 
2520  @Messages({
2521  "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
2522  })
2523  private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
2524  /*
2525  * TODO (JIRA-2180): Always create the case directory as part of the
2526  * case creation process.
2527  */
2528  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2529  if (new File(metadata.getCaseDirectory()).exists() == false) {
2530  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
2531  Case.createCaseDirectory(metadata.getCaseDirectory(), metadata.getCaseType());
2532  }
2533  }
2534 
2541  @Messages({
2542  "Case.progressMessage.switchingLogDirectory=Switching log directory..."
2543  })
2544  private void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator) {
2545  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
2547  }
2548 
2560  @Messages({
2561  "Case.progressMessage.savingCaseMetadata=Saving case metadata to file...",
2562  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0}."
2563  })
2564  private void saveCaseMetadataToFile(ProgressIndicator progressIndicator) throws CaseActionException {
2565  progressIndicator.progress(Bundle.Case_progressMessage_savingCaseMetadata());
2566  try {
2567  this.metadata.writeToFile();
2568  } catch (CaseMetadataException ex) {
2569  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveCaseMetadata(ex.getLocalizedMessage()), ex);
2570  }
2571  }
2572 
2584  @Messages({
2585  "Case.progressMessage.creatingCaseNodeData=Creating coordination service node data...",
2586  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseNodeData=Failed to create coordination service node data:\n{0}."
2587  })
2588  private void createCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2590  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
2591  try {
2592  CaseNodeData.createCaseNodeData(metadata);
2593  } catch (CaseNodeDataException | InterruptedException ex) {
2594  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
2595  }
2596  }
2597  }
2598 
2610  @Messages({
2611  "Case.progressMessage.updatingCaseNodeData=Updating coordination service node data...",
2612  "# {0} - exception message", "Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}."
2613  })
2614  private void updateCaseNodeData(ProgressIndicator progressIndicator) throws CaseActionException {
2616  progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
2617  try {
2619  nodeData.setLastAccessDate(new Date());
2620  CaseNodeData.writeCaseNodeData(nodeData);
2621  } catch (CaseNodeDataException | InterruptedException ex) {
2622  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
2623  }
2624  }
2625  }
2626 
2632  @Messages({
2633  "Case.progressMessage.clearingTempDirectory=Clearing case temp directory..."
2634  })
2635  private void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator) {
2636  /*
2637  * Clear the temp subdirectory of the case directory.
2638  */
2639  progressIndicator.progress(Bundle.Case_progressMessage_clearingTempDirectory());
2640  FileUtil.deleteDir(new File(this.getTempDirectory()));
2641  }
2642 
2654  @Messages({
2655  "Case.progressMessage.creatingCaseDatabase=Creating case database...",
2656  "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.",
2657  "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.",
2658  "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}."
2659  })
2660  private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException {
2661  progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase());
2662  try {
2663  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2664  /*
2665  * For single-user cases, the case database is a SQLite database
2666  * with a standard name, physically located in the case
2667  * directory.
2668  */
2669  caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString());
2670  metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
2671  } else {
2672  /*
2673  * For multi-user cases, the case database is a PostgreSQL
2674  * database with a name derived from the case display name,
2675  * physically located on the PostgreSQL database server.
2676  */
2677  caseDb = SleuthkitCase.newCase(metadata.getCaseDisplayName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2678  metadata.setCaseDatabaseName(caseDb.getDatabaseName());
2679  }
2680  } catch (TskCoreException ex) {
2681  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
2682  } catch (UserPreferencesException ex) {
2683  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2684  } catch (CaseMetadataException ex) {
2685  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotSaveDbNameToMetadataFile(ex.getLocalizedMessage()), ex);
2686  }
2687  }
2688 
2700  @Messages({
2701  "Case.progressMessage.openingCaseDatabase=Opening case database...",
2702  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
2703  "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
2704  "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
2705  })
2706  private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
2707  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase());
2708  try {
2709  String databaseName = metadata.getCaseDatabaseName();
2710  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2711  caseDb = SleuthkitCase.openCase(Paths.get(metadata.getCaseDirectory(), databaseName).toString());
2713  caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory());
2714  } else {
2715  throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
2716  }
2718  } catch (TskUnsupportedSchemaVersionException ex) {
2719  throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
2720  } catch (UserPreferencesException ex) {
2721  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex);
2722  } catch (TskCoreException ex) {
2723  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex);
2724  }
2725  }
2726 
2733  @Messages({
2734  "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",})
2735  private void openCaseLevelServices(ProgressIndicator progressIndicator) {
2736  progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
2737  this.caseServices = new Services(caseDb);
2738  /*
2739  * RC Note: JM put this initialization here. I'm not sure why. However,
2740  * my attempt to put it in the openCaseDatabase method seems to lead to
2741  * intermittent unchecked exceptions concerning a missing subscriber.
2742  */
2743  caseDb.registerForEvents(sleuthkitEventListener);
2744  }
2745 
2758  @NbBundle.Messages({
2759  "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
2760  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
2761  "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
2762  "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
2763  })
2764  private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException {
2765  /*
2766  * Each service gets its own independently cancellable/interruptible
2767  * task, running in a named thread managed by an executor service, with
2768  * its own progress indicator. This allows for cancellation of the
2769  * opening of case resources for individual services. It also makes it
2770  * possible to ensure that each service task completes before the next
2771  * one starts by awaiting termination of the executor service.
2772  */
2773  progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
2774 
2775  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
2776  )) {
2777  /*
2778  * Create a progress indicator for the task and start the task. If
2779  * running with a GUI, the progress indicator will be a dialog box
2780  * with a Cancel button.
2781  */
2782  CancelButtonListener cancelButtonListener = null;
2783  ProgressIndicator appServiceProgressIndicator;
2785  cancelButtonListener = new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
2786  appServiceProgressIndicator = new ModalDialogProgressIndicator(
2787  mainFrame,
2788  Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
2789  new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
2790  Bundle.Case_progressIndicatorCancelButton_label(),
2791  cancelButtonListener);
2792  } else {
2793  appServiceProgressIndicator = new LoggingProgressIndicator();
2794  }
2795  appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing());
2796  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase);
2797  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2798  threadNameSuffix = threadNameSuffix.toLowerCase();
2799  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
2800  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2801  Future<Void> future = executor.submit(() -> {
2802  service.openCaseResources(context);
2803  return null;
2804  });
2805  if (null != cancelButtonListener) {
2806  cancelButtonListener.setCaseContext(context);
2807  cancelButtonListener.setCaseActionFuture(future);
2808  }
2809 
2810  /*
2811  * Wait for the task to either be completed or
2812  * cancelled/interrupted, or for the opening of the case to be
2813  * cancelled.
2814  */
2815  try {
2816  future.get();
2817  } catch (InterruptedException discarded) {
2818  /*
2819  * The parent create/open case task has been cancelled.
2820  */
2821  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()));
2822  future.cancel(true);
2823  } catch (CancellationException discarded) {
2824  /*
2825  * The opening of case resources by the application service has
2826  * been cancelled, so the executor service has thrown. Note that
2827  * there is no guarantee the task itself has responded to the
2828  * cancellation request yet.
2829  */
2830  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()));
2831  } catch (ExecutionException ex) {
2832  /*
2833  * An exception was thrown while executing the task. The
2834  * case-specific application service resources are not
2835  * essential. Log an error and notify the user if running the
2836  * desktop GUI, but do not throw.
2837  */
2838  Case.logger.log(Level.SEVERE, String.format("%s failed to open case resources for %s", service.getServiceName(), this.getDisplayName()), ex);
2840  SwingUtilities.invokeLater(() -> {
2841  MessageNotifyUtil.Notify.error(Bundle.Case_servicesException_notificationTitle(service.getServiceName()), ex.getLocalizedMessage());
2842  });
2843  }
2844  } finally {
2845  /*
2846  * Shut down the executor service and wait for it to finish.
2847  * This ensures that the task has finished. Without this, it
2848  * would be possible to start the next task before the current
2849  * task responded to a cancellation request.
2850  */
2852  appServiceProgressIndicator.finish();
2853  }
2855  }
2856  }
2857 
2869  @Messages({
2870  "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",
2871  "# {0} - exception message", "Case.exceptionMessage.couldNotOpenRemoteEventChannel=Failed to open remote events channel:\n{0}.",
2872  "# {0} - exception message", "Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}."
2873  })
2874  private void openCommunicationChannels(ProgressIndicator progressIndicator) throws CaseActionException {
2875  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2876  progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
2877  try {
2878  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
2880  collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
2881  } catch (AutopsyEventException ex) {
2882  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
2883  } catch (CollaborationMonitor.CollaborationMonitorException ex) {
2884  throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreatCollaborationMonitor(ex.getLocalizedMessage()), ex);
2885  }
2886  }
2887  }
2888 
2903  private void doCloseCaseAction() throws CaseActionException {
2904  /*
2905  * Set up either a GUI progress indicator without a Cancel button or a
2906  * logging progress indicator.
2907  */
2908  ProgressIndicator progressIndicator;
2910  progressIndicator = new ModalDialogProgressIndicator(
2911  mainFrame,
2912  Bundle.Case_progressIndicatorTitle_closingCase());
2913  } else {
2914  progressIndicator = new LoggingProgressIndicator();
2915  }
2916  progressIndicator.start(Bundle.Case_progressMessage_preparing());
2917 
2918  /*
2919  * Closing a case is always done in the same non-UI thread that
2920  * opened/created the case. If the case is a multi-user case, this
2921  * ensures that case lock that is held as long as the case is open is
2922  * released in the same thread in which it was acquired, as is required
2923  * by the coordination service.
2924  */
2925  Future<Void> future = caseActionExecutor.submit(() -> {
2926  if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
2927  close(progressIndicator);
2928  } else {
2929  /*
2930  * Acquire an exclusive case resources lock to ensure only one
2931  * node at a time can create/open/upgrade/close the case
2932  * resources.
2933  */
2934  progressIndicator.progress(Bundle.Case_progressMessage_preparing());
2935  try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
2936  if (null == resourcesLock) {
2937  throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
2938  }
2939  close(progressIndicator);
2940  } finally {
2941  /*
2942  * Always release the case directory lock that was acquired
2943  * when the case was opened.
2944  */
2945  releaseCaseLock();
2946  }
2947  }
2948  return null;
2949  });
2950 
2951  try {
2952  future.get();
2953  } catch (InterruptedException | CancellationException unused) {
2954  /*
2955  * The wait has been interrupted by interrupting the thread running
2956  * this method. Not allowing cancellation of case closing, so ignore
2957  * the interrupt. Likewise, cancellation of the case closing task is
2958  * not supported.
2959  */
2960  } catch (ExecutionException ex) {
2961  throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2962  } finally {
2963  ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
2964  progressIndicator.finish();
2965  }
2966  }
2967 
2973  @Messages({
2974  "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2975  "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2976  "Case.progressMessage.closingCaseDatabase=Closing case database..."
2977  })
2978  private void close(ProgressIndicator progressIndicator) {
2980 
2981  /*
2982  * Stop sending/receiving case events to and from other nodes if this is
2983  * a multi-user case.
2984  */
2985  if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
2986  progressIndicator.progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2987  if (null != collaborationMonitor) {
2988  collaborationMonitor.shutdown();
2989  }
2990  eventPublisher.closeRemoteEventChannel();
2991  }
2992 
2993  /*
2994  * Allow all registered application services providers to close
2995  * resources related to the case.
2996  */
2997  progressIndicator.progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2999 
3000  /*
3001  * Close the case database.
3002  */
3003  if (null != caseDb) {
3004  progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
3005  caseDb.unregisterForEvents(sleuthkitEventListener);
3006  caseDb.close();
3007  }
3008 
3012  deleteTempfilesFromCaseDirectory(progressIndicator);
3013 
3014  /*
3015  * Switch the log directory.
3016  */
3017  progressIndicator.progress(Bundle.Case_progressMessage_switchingLogDirectory());
3019  }
3020 
3025  @Messages({
3026  "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
3027  "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
3028  })
3030  /*
3031  * Each service gets its own independently cancellable task, and thus
3032  * its own task progress indicator.
3033  */
3034  for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
3035  )) {
3036  ProgressIndicator progressIndicator;
3038  progressIndicator = new ModalDialogProgressIndicator(
3039  mainFrame,
3040  Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
3041  } else {
3042  progressIndicator = new LoggingProgressIndicator();
3043  }
3044  progressIndicator.start(Bundle.Case_progressMessage_preparing());
3045  AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, progressIndicator);
3046  String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
3047  threadNameSuffix = threadNameSuffix.toLowerCase();
3048  TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix));
3049  ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
3050  Future<Void> future = executor.submit(() -> {
3051  service.closeCaseResources(context);
3052  return null;
3053  });
3054  try {
3055  future.get();
3056  } catch (InterruptedException ex) {
3057  Case.logger.log(Level.SEVERE, String.format("Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
3058  } catch (CancellationException ex) {
3059  Case.logger.log(Level.SEVERE, String.format("Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
3060  } catch (ExecutionException ex) {
3061  Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", service.getServiceName()), ex);
3063  SwingUtilities.invokeLater(() -> MessageNotifyUtil.Notify.error(
3064  Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
3065  Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
3066  }
3067  } finally {
3069  progressIndicator.finish();
3070  }
3071  }
3072  }
3073 
3079  @Messages({
3080  "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.",
3081  "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case."
3082  })
3083  private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
3084  String caseDir = metadata.getCaseDirectory();
3085  try {
3086  CoordinationService coordinationService = CoordinationService.getInstance();
3087  caseLock = lockType == CaseLockType.SHARED
3088  ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
3089  : coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
3090  if (caseLock == null) {
3091  if (lockType == CaseLockType.SHARED) {
3092  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
3093  } else {
3094  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
3095  }
3096  }
3097  } catch (InterruptedException | CoordinationServiceException ex) {
3098  if (lockType == CaseLockType.SHARED) {
3099  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
3100  } else {
3101  throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
3102  }
3103  }
3104  }
3105 
3109  private void releaseCaseLock() {
3110  if (caseLock != null) {
3111  try {
3112  caseLock.release();
3113  caseLock = null;
3115  logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
3116  }
3117  }
3118  }
3119 
3126  private String getOrCreateSubdirectory(String subDirectoryName) {
3127  File subDirectory = Paths.get(getOutputDirectory(), subDirectoryName).toFile();
3128  if (!subDirectory.exists()) {
3129  subDirectory.mkdirs();
3130  }
3131  return subDirectory.toString();
3132 
3133  }
3134 
3146  @Messages({
3147  "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details."
3148  })
3149  private static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3150  boolean errorsOccurred = false;
3151  try {
3152  deleteTextIndex(metadata, progressIndicator);
3153  } catch (KeywordSearchServiceException ex) {
3154  errorsOccurred = true;
3155  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
3156  }
3157 
3158  try {
3159  deleteCaseDirectory(metadata, progressIndicator);
3160  } catch (CaseActionException ex) {
3161  errorsOccurred = true;
3162  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
3163  }
3164 
3165  deleteFromRecentCases(metadata, progressIndicator);
3166 
3167  if (errorsOccurred) {
3168  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3169  }
3170  }
3171 
3191  @Messages({
3192  "Case.progressMessage.connectingToCoordSvc=Connecting to coordination service...",
3193  "# {0} - exception message", "Case.exceptionMessage.failedToConnectToCoordSvc=Failed to connect to coordination service:\n{0}.",
3194  "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.",
3195  "# {0} - exception message", "Case.exceptionMessage.failedToLockCaseForDeletion=Failed to exclusively lock case for deletion:\n{0}.",
3196  "Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...",
3197  "# {0} - exception message", "Case.exceptionMessage.failedToFetchCoordSvcNodeData=Failed to fetch coordination service node data:\n{0}.",
3198  "Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...",
3199  "Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node..."
3200  })
3201  private static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException, InterruptedException {
3202  progressIndicator.progress(Bundle.Case_progressMessage_connectingToCoordSvc());
3203  CoordinationService coordinationService;
3204  try {
3205  coordinationService = CoordinationService.getInstance();
3206  } catch (CoordinationServiceException ex) {
3207  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
3208  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToConnectToCoordSvc(ex.getLocalizedMessage()));
3209  }
3210 
3211  CaseNodeData caseNodeData;
3212  boolean errorsOccurred = false;
3213  try (CoordinationService.Lock dirLock = coordinationService.tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) {
3214  if (dirLock == null) {
3215  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
3216  throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase());
3217  }
3218 
3219  progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
3220  try {
3221  caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
3222  } catch (CaseNodeDataException | InterruptedException ex) {
3223  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
3224  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
3225  }
3226 
3227  errorsOccurred = deleteMultiUserCase(caseNodeData, metadata, progressIndicator, logger);
3228 
3229  progressIndicator.progress(Bundle.Case_progressMessage_deletingResourcesCoordSvcNode());
3230  try {
3231  String resourcesLockNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory());
3232  coordinationService.deleteNode(CategoryNode.CASES, resourcesLockNodePath);
3233  } catch (CoordinationServiceException ex) {
3234  if (!isNoNodeException(ex)) {
3235  errorsOccurred = true;
3236  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
3237  }
3238  } catch (InterruptedException ex) {
3239  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
3240  }
3241 
3242  } catch (CoordinationServiceException ex) {
3243  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
3244  throw new CaseActionException(Bundle.Case_exceptionMessage_failedToLockCaseForDeletion(ex.getLocalizedMessage()));
3245  }
3246 
3247  if (!errorsOccurred) {
3248  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirCoordSvcNode());
3249  try {
3250  String casDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory());
3251  coordinationService.deleteNode(CategoryNode.CASES, casDirNodePath);
3252  } catch (CoordinationServiceException | InterruptedException ex) {
3253  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
3254  errorsOccurred = true;
3255  }
3256  }
3257 
3258  if (errorsOccurred) {
3259  throw new CaseActionException(Bundle.Case_exceptionMessage_errorsDeletingCase());
3260  }
3261  }
3262 
3287  @Beta
3288  public static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws InterruptedException {
3289  boolean errorsOccurred = false;
3290  try {
3291  deleteMultiUserCaseDatabase(caseNodeData, metadata, progressIndicator, logger);
3292  deleteMultiUserCaseTextIndex(caseNodeData, metadata, progressIndicator, logger);
3293  deleteMultiUserCaseDirectory(caseNodeData, metadata, progressIndicator, logger);
3294  deleteFromRecentCases(metadata, progressIndicator);
3295  } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
3296  errorsOccurred = true;
3297  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
3298  } catch (KeywordSearchServiceException ex) {
3299  errorsOccurred = true;
3300  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
3301  } catch (CaseActionException ex) {
3302  errorsOccurred = true;
3303  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
3304  }
3305  return errorsOccurred;
3306  }
3307 
3328  @Messages({
3329  "Case.progressMessage.deletingCaseDatabase=Deleting case database..."
3330  })
3331  private static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws UserPreferencesException, ClassNotFoundException, SQLException, InterruptedException {
3332  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DB)) {
3333  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDatabase());
3334  logger.log(Level.INFO, String.format("Deleting case database for %s (%s) in %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3335  CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
3336  String url = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres"; //NON-NLS
3337  Class.forName("org.postgresql.Driver"); //NON-NLS
3338  try (Connection connection = DriverManager.getConnection(url, info.getUserName(), info.getPassword()); Statement statement = connection.createStatement()) {
3339  String dbExistsQuery = "SELECT 1 from pg_database WHERE datname = '" + metadata.getCaseDatabaseName() + "'"; //NON-NLS
3340  try (ResultSet queryResult = statement.executeQuery(dbExistsQuery)) {
3341  if (queryResult.next()) {
3342  String deleteCommand = "DROP DATABASE \"" + metadata.getCaseDatabaseName() + "\""; //NON-NLS
3343  statement.execute(deleteCommand);
3344  }
3345  }
3346  }
3348  }
3349  }
3350 
3366  private static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws KeywordSearchServiceException, InterruptedException {
3368  logger.log(Level.INFO, String.format("Deleting text index for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3369  deleteTextIndex(metadata, progressIndicator);
3371  }
3372  }
3373 
3383  @Messages({
3384  "Case.progressMessage.deletingTextIndex=Deleting text index..."
3385  })
3386  private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
3387  progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
3388 
3389  for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
3390  )) {
3391  searchService.deleteTextIndex(metadata);
3392  }
3393  }
3394 
3409  private static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger) throws CaseActionException, InterruptedException {
3410  if (!caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR)) {
3411  logger.log(Level.INFO, String.format("Deleting case directory for %s", caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory())); //NON-NLS
3412  deleteCaseDirectory(metadata, progressIndicator);
3414  }
3415  }
3416 
3426  @Messages({
3427  "Case.progressMessage.deletingCaseDirectory=Deleting case directory..."
3428  })
3429  private static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator) throws CaseActionException {
3430  progressIndicator.progress(Bundle.Case_progressMessage_deletingCaseDirectory());
3431  if (!FileUtil.deleteDir(new File(metadata.getCaseDirectory()))) {
3432  throw new CaseActionException(String.format("Failed to delete %s", metadata.getCaseDirectory())); //NON-NLS
3433  }
3434  }
3435 
3443  @Messages({
3444  "Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu..."
3445  })
3446  private static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator) {
3448  progressIndicator.progress(Bundle.Case_progressMessage_removingCaseFromRecentCases());
3449  SwingUtilities.invokeLater(() -> {
3450  RecentCases.getInstance().removeRecentCase(metadata.getCaseDisplayName(), metadata.getFilePath().toString());
3451  });
3452  }
3453  }
3454 
3465  boolean isNodeNodeEx = false;
3466  Throwable cause = ex.getCause();
3467  if (cause != null) {
3468  String causeMessage = cause.getMessage();
3469  isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT);
3470  }
3471  return isNodeNodeEx;
3472  }
3473 
3485  private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
3486  try {
3487  caseNodeData.setDeletedFlag(flag);
3488  CaseNodeData.writeCaseNodeData(caseNodeData);
3489  } catch (CaseNodeDataException ex) {
3490  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);
3491 
3492  }
3493  }
3494 
3506  private void updateDataParameters() throws TskCoreException {
3507  hasDataSource = dbHasDataSource();
3508 
3509  if (!hasDataSource) {
3510  hasData = dbHasData();
3511  } else {
3512  hasData = true;
3513  }
3514  }
3515 
3523  private boolean dbHasDataSource() throws TskCoreException {
3524  String query = "SELECT count(*) AS count FROM (SELECT * FROM data_source_info LIMIT 1)t";
3525  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3526  ResultSet resultSet = dbQuery.getResultSet();
3527  if (resultSet.next()) {
3528  return resultSet.getLong("count") > 0;
3529  }
3530  return false;
3531  } catch (SQLException ex) {
3532  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3533  throw new TskCoreException("Error accessing case databse", ex);
3534  }
3535  }
3536 
3545  private boolean dbHasData() throws TskCoreException {
3546  // The LIMIT 1 in the subquery should limit the data returned and
3547  // make the overall query more efficent.
3548  String query = "SELECT SUM(cnt) total FROM "
3549  + "(SELECT COUNT(*) AS cnt FROM "
3550  + "(SELECT * FROM tsk_objects LIMIT 1)t "
3551  + "UNION ALL "
3552  + "SELECT COUNT(*) AS cnt FROM "
3553  + "(SELECT * FROM tsk_hosts LIMIT 1)r) s";
3554  try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) {
3555  ResultSet resultSet = dbQuery.getResultSet();
3556  if (resultSet.next()) {
3557  return resultSet.getLong("total") > 0;
3558  } else {
3559  return false;
3560  }
3561  } catch (SQLException ex) {
3562  logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
3563  throw new TskCoreException("Error accessing case databse", ex);
3564  }
3565  }
3566 
3575  private interface CaseAction<T, V, R> {
3576 
3587  R execute(T progressIndicator, V additionalParams) throws CaseActionException;
3588  }
3589 
3594  private enum CaseLockType {
3595  SHARED, EXCLUSIVE;
3596  }
3597 
3602  @ThreadSafe
3603  private final static class CancelButtonListener implements ActionListener {
3604 
3605  private final String cancellationMessage;
3606  @GuardedBy("this")
3607  private boolean cancelRequested;
3608  @GuardedBy("this")
3610  @GuardedBy("this")
3611  private Future<?> caseActionFuture;
3612 
3621  private CancelButtonListener(String cancellationMessage) {
3622  this.cancellationMessage = cancellationMessage;
3623  }
3624 
3630  private synchronized void setCaseContext(CaseContext caseContext) {
3631  this.caseContext = caseContext;
3632  /*
3633  * If the cancel button has already been pressed, pass the
3634  * cancellation on to the case context.
3635  */
3636  if (cancelRequested) {
3637  cancel();
3638  }
3639  }
3640 
3646  private synchronized void setCaseActionFuture(Future<?> caseActionFuture) {
3647  this.caseActionFuture = caseActionFuture;
3648  /*
3649  * If the cancel button has already been pressed, cancel the Future
3650  * of the task.
3651  */
3652  if (cancelRequested) {
3653  cancel();
3654  }
3655  }
3656 
3662  @Override
3663  public synchronized void actionPerformed(ActionEvent event) {
3664  cancel();
3665  }
3666 
3670  private void cancel() {
3671  /*
3672  * At a minimum, set the cancellation requested flag of this
3673  * listener.
3674  */
3675  this.cancelRequested = true;
3676  if (null != this.caseContext) {
3677  /*
3678  * Set the cancellation request flag and display the
3679  * cancellation message in the progress indicator for the case
3680  * context associated with this listener.
3681  */
3683  ProgressIndicator progressIndicator = this.caseContext.getProgressIndicator();
3684  if (progressIndicator instanceof ModalDialogProgressIndicator) {
3685  ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
3686  }
3687  }
3688  this.caseContext.requestCancel();
3689  }
3690  if (null != this.caseActionFuture) {
3691  /*
3692  * Cancel the Future of the task associated with this listener.
3693  * Note that the task thread will be interrupted if the task is
3694  * blocked.
3695  */
3696  this.caseActionFuture.cancel(true);
3697  }
3698  }
3699  }
3700 
3704  private static class TaskThreadFactory implements ThreadFactory {
3705 
3706  private final String threadName;
3707 
3708  private TaskThreadFactory(String threadName) {
3709  this.threadName = threadName;
3710  }
3711 
3712  @Override
3713  public Thread newThread(Runnable task) {
3714  return new Thread(task, threadName);
3715  }
3716 
3717  }
3718 
3726  @Deprecated
3727  public static String getAppName() {
3728  return UserPreferences.getAppName();
3729  }
3730 
3750  @Deprecated
3751  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner) throws CaseActionException {
3752  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
3753  }
3754 
3775  @Deprecated
3776  public static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
3777  createAsCurrentCase(caseDir, caseDisplayName, caseNumber, examiner, caseType);
3778  }
3779 
3791  @Deprecated
3792  public static void open(String caseMetadataFilePath) throws CaseActionException {
3793  openAsCurrentCase(caseMetadataFilePath);
3794  }
3795 
3805  @Deprecated
3806  public void closeCase() throws CaseActionException {
3807  closeCurrentCase();
3808  }
3809 
3815  @Deprecated
3816  public static void invokeStartupDialog() {
3818  }
3819 
3833  @Deprecated
3834  public static String convertTimeZone(String timeZoneId) {
3835  return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
3836  }
3837 
3847  @Deprecated
3848  public static boolean pathExists(String filePath) {
3849  return new File(filePath).isFile();
3850  }
3851 
3860  @Deprecated
3861  public static String getAutopsyVersion() {
3862  return Version.getVersion();
3863  }
3864 
3872  @Deprecated
3873  public static boolean existsCurrentCase() {
3874  return isCaseOpen();
3875  }
3876 
3886  @Deprecated
3887  public static String getModulesOutputDirRelPath() {
3888  return "ModuleOutput"; //NON-NLS
3889  }
3890 
3900  @Deprecated
3901  public static PropertyChangeSupport
3903  return new PropertyChangeSupport(Case.class
3904  );
3905  }
3906 
3915  @Deprecated
3916  public String getModulesOutputDirAbsPath() {
3917  return getModuleDirectory();
3918  }
3919 
3934  @Deprecated
3935  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
3936  try {
3937  Image newDataSource = caseDb.getImageById(imgId);
3938  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
3939  return newDataSource;
3940  } catch (TskCoreException ex) {
3941  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
3942  }
3943  }
3944 
3952  @Deprecated
3953  public Set<TimeZone> getTimeZone() {
3954  return getTimeZones();
3955  }
3956 
3967  @Deprecated
3968  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
3969  deleteReports(reports);
3970  }
3971 
3972 }
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:183
List< Content > getDataSources()
Definition: Case.java:1668
static CaseNodeData createCaseNodeData(final CaseMetadata metadata)
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:1811
Case(CaseMetadata caseMetaData)
Definition: Case.java:2042
static CaseType fromString(String typeName)
Definition: Case.java:229
final SleuthkitEventListener sleuthkitEventListener
Definition: Case.java:194
static final String CASE_ACTION_THREAD_NAME
Definition: Case.java:179
static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag)
Definition: Case.java:3485
static void createAsCurrentCase(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:793
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:1876
Void open(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2237
CoordinationService.Lock caseLock
Definition: Case.java:192
static boolean deleteMultiUserCase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3288
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:3935
void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event)
Definition: Case.java:529
static synchronized IngestManager getInstance()
void deleteNode(CategoryNode category, String nodePath)
static final ExecutorService openFileSystemsExecutor
Definition: Case.java:187
static final String NO_NODE_ERROR_MSG_FRAGMENT
Definition: Case.java:181
void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event)
Definition: Case.java:606
void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event)
Definition: Case.java:578
void acquireCaseLock(CaseLockType lockType)
Definition: Case.java:3083
static boolean existsCurrentCase()
Definition: Case.java:3873
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:651
void start(String message, int totalWorkUnits)
static final Logger logger
Definition: Case.java:182
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List< BlackboardArtifactTag > removedTagList)
Definition: Case.java:1865
static Future<?> backgroundOpenFileSystemsFuture
Definition: Case.java:185
static final String EXPORT_FOLDER
Definition: Case.java:174
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:1135
void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event)
Definition: Case.java:544
void notifyTagDefinitionChanged(String changedTagName)
Definition: Case.java:1822
void notifyContentTagAdded(ContentTag newTag, List< ContentTag > deletedTagList)
Definition: Case.java:1800
static volatile Frame mainFrame
Definition: Case.java:188
static String convertTimeZone(String timeZoneId)
Definition: Case.java:3834
static boolean driveExists(String path)
Definition: DriveUtils.java:66
static void deleteSingleUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3149
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event)
Definition: Case.java:622
void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event)
Definition: Case.java:499
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:89
volatile ExecutorService caseActionExecutor
Definition: Case.java:191
void notifyCentralRepoCommentChanged(long contentId, String newComment)
Definition: Case.java:1837
static final String APP_NAME
Definition: Case.java:169
static final String CACHE_FOLDER
Definition: Case.java:173
static void deleteMultiUserCaseDirectory(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3409
Case(CaseType caseType, String caseDir, CaseDetails caseDetails)
Definition: Case.java:2033
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1891
static void updateGUIForCaseOpened(Case newCurrentCase)
Definition: Case.java:1253
void notifyDataSourceNameChanged(Content dataSource, String newName)
Definition: Case.java:1776
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:3776
void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event)
Definition: Case.java:617
static final int CASE_LOCK_TIMEOUT_MINS
Definition: Case.java:167
static final String SINGLE_USER_CASE_DB_NAME
Definition: Case.java:171
static void deleteCase(CaseMetadata metadata)
Definition: Case.java:997
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:3968
synchronized void setCaseContext(CaseContext caseContext)
Definition: Case.java:3630
static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS
Definition: Case.java:168
static boolean isValidName(String caseName)
Definition: Case.java:735
void openCaseDataBase(ProgressIndicator progressIndicator)
Definition: Case.java:2706
void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator)
Definition: Case.java:2523
CollaborationMonitor collaborationMonitor
Definition: Case.java:195
void openCommunicationChannels(ProgressIndicator progressIndicator)
Definition: Case.java:2874
static void shutDownTaskExecutor(ExecutorService executor)
static String getModulesOutputDirRelPath()
Definition: Case.java:3887
void saveCaseMetadataToFile(ProgressIndicator progressIndicator)
Definition: Case.java:2564
static void deleteMultiUserCaseTextIndex(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3366
void doOpenCaseAction(String progressIndicatorTitle, CaseAction< ProgressIndicator, Object, Void > caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams)
Definition: Case.java:2083
void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event)
Definition: Case.java:627
synchronized void actionPerformed(ActionEvent event)
Definition: Case.java:3663
static final String MODULE_FOLDER
Definition: Case.java:178
void openCaseLevelServices(ProgressIndicator progressIndicator)
Definition: Case.java:2735
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
Definition: Case.java:3751
synchronized void openRemoteEventChannel(String channelName)
void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event)
Definition: Case.java:595
void createCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2588
void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event)
Definition: Case.java:505
static String displayNameToUniqueName(String caseDisplayName)
Definition: Case.java:1095
static void deleteFromRecentCases(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3446
static void openAsCurrentCase(String caseMetadataFilePath)
Definition: Case.java:820
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:711
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static boolean isNoNodeException(CoordinationServiceException ex)
Definition: Case.java:3464
void fireModuleDataEvent(ModuleDataEvent moduleDataEvent)
default void setCancelling(String cancellingMessage)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:2978
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:1852
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:3902
void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event)
Definition: Case.java:534
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:639
void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event)
Definition: Case.java:567
synchronized void setCaseActionFuture(Future<?> caseActionFuture)
Definition: Case.java:3646
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void createCaseDatabase(ProgressIndicator progressIndicator)
Definition: Case.java:2660
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:701
void switchLoggingToCaseLogsDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2544
static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3386
void deleteTempfilesFromCaseDirectory(ProgressIndicator progressIndicator)
Definition: Case.java:2635
void deleteReports(Collection<?extends Report > reports)
Definition: Case.java:1948
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
Definition: Case.java:1763
Report addReport(String localPath, String srcModuleName, String reportName, Content parent)
Definition: Case.java:1909
static boolean pathExists(String filePath)
Definition: Case.java:3848
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2468
R execute(T progressIndicator, V additionalParams)
static void open(String caseMetadataFilePath)
Definition: Case.java:3792
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
Definition: Case.java:1046
static CoordinationService.Lock acquireCaseResourcesLock(String caseDir)
Definition: Case.java:1227
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
Definition: Case.java:3126
boolean equalsName(String otherTypeName)
Definition: Case.java:287
static final String EVENT_CHANNEL_NAME
Definition: Case.java:172
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event)
Definition: Case.java:523
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:676
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Definition: Case.java:175
Void create(ProgressIndicator progressIndicator, Object additionalParams)
Definition: Case.java:2185
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static volatile Case currentCase
Definition: Case.java:189
void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase)
Definition: Case.java:2764
void notifyAddingDataSource(UUID eventId)
Definition: Case.java:1732
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:691
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:1787
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
Definition: Case.java:180
void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event)
Definition: Case.java:555
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:766
static void deleteCaseDirectory(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3429
static synchronized DirectoryTreeTopComponent findInstance()
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
Definition: Case.java:1748
static void deleteMultiUserCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
Definition: Case.java:3201
static final String CONFIG_FOLDER
Definition: Case.java:177
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:721
static final Object caseActionSerializationLock
Definition: Case.java:184
static void deleteMultiUserCaseDatabase(CaseNodeData caseNodeData, CaseMetadata metadata, ProgressIndicator progressIndicator, Logger logger)
Definition: Case.java:3331
static final String REPORTS_FOLDER
Definition: Case.java:176
static final String TEMP_FOLDER
Definition: Case.java:170
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:666
void updateCaseNodeData(ProgressIndicator progressIndicator)
Definition: Case.java:2614
static synchronized IngestServices getInstance()

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