1 /*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2011-2017 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
21 import java.awt.Frame;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.beans.PropertyChangeListener;
25 import java.beans.PropertyChangeSupport;
26 import java.io.File;
27 import java.io.IOException;
28 import java.nio.file.InvalidPathException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.sql.Connection;
32 import java.sql.DriverManager;
33 import java.sql.SQLException;
34 import java.sql.Statement;
35 import java.text.SimpleDateFormat;
36 import java.util.Collection;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.MissingResourceException;
43 import java.util.Set;
44 import java.util.TimeZone;
45 import java.util.UUID;
46 import java.util.concurrent.CancellationException;
47 import java.util.concurrent.ExecutionException;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors;
50 import java.util.concurrent.Future;
51 import java.util.concurrent.ThreadFactory;
52 import java.util.concurrent.TimeUnit;
53 import java.util.logging.Level;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56 import javax.annotation.concurrent.GuardedBy;
57 import javax.annotation.concurrent.ThreadSafe;
58 import javax.swing.JOptionPane;
59 import javax.swing.SwingUtilities;
60 import org.openide.util.Lookup;
61 import org.openide.util.NbBundle;
62 import org.openide.util.NbBundle.Messages;
63 import org.openide.util.actions.CallableSystemAction;
64 import org.openide.windows.WindowManager;
113
118
145
146 /*
147 * Get a reference to the main window of the desktop application to use to
148 * parent pop up dialogs and initialize the application name for use in
149 * changing the main window title.
150 */
151 static {
152 WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
153 @Override
154 public void run() {
155 mainFrame = WindowManager.getDefault().getMainWindow();
156 }
157 });
158 }
159
164
167
169
178 if (typeName != null) {
180 if (typeName.equalsIgnoreCase(c.toString())) {
181 return c;
182 }
183 }
184 }
185 return null;
186 }
187
193 @Override
195 return typeName;
196 }
197
203 @Messages({
204 "Case_caseType_singleUser=Single-user case",
205 "Case_caseType_multiUser=Multi-user case"
206 })
208 if (fromString(typeName) == SINGLE_USER_CASE) {
209 return Bundle.Case_caseType_singleUser();
210 } else {
211 return Bundle.Case_caseType_multiUser();
212 }
213 }
214
221 this.typeName = typeName;
222 }
223
234 @Deprecated
236 return (otherTypeName == null) ? false : typeName.equals(otherTypeName);
237 }
238
239 };
240
246
349 };
350
359 .map(Events::toString)
360 .collect(Collectors.toSet()), listener);
361 }
362
371 .map(Events::toString)
372 .collect(Collectors.toSet()), listener);
373 }
374
383 }
384
393 }
394
403 }
404
413 }
414
424 return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
425 || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
426 || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
427 }
428
450 @Messages({
451 "Case.exceptionMessage.emptyCaseName=Must specify a case name.",
452 "Case.exceptionMessage.emptyCaseDir=Must specify a case directory path."
453 })
455 if (caseDisplayName.isEmpty()) {
457 }
458 if (caseDir.isEmpty()) {
460 }
462 }
463
477 @Messages({
478 "Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata.",
479 "Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case."
480 })
483 try {
484 metadata =
new CaseMetadata(Paths.get(caseMetadataFilePath));
487 }
489 throw new CaseActionException(Bundle.Case_exceptionMessage_cannotOpenMultiUserCaseNoSettings());
490 }
492 }
493
500 return currentCase != null;
501 }
502
511 /*
512 * Throwing an unchecked exception is a bad idea here.
513 *
514 * TODO (JIRA-2229): Case.getCurrentCase() method throws unchecked
515 * IllegalStateException; change to throw checked exception or return
516 * null
517 */
518 if (null != currentCase) {
520 } else {
521 throw new IllegalStateException(NbBundle.getMessage(
Case.class,
"Case.getCurCase.exception.noneOpen"));
522 }
523 }
524
533 @Messages({
534 "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}",
535 "Case.progressIndicatorTitle.closingCase=Closing Case"
536 })
539 if (null == currentCase) {
540 return;
541 }
543 try {
545 logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
546 currentCase = null;
548 logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
551 throw ex;
552 } finally {
555 }
556 }
557 }
558 }
559
570 if (null == currentCase) {
571 return;
572 }
576 }
577 }
578
590 @Messages({
591 "Case.progressIndicatorTitle.deletingCase=Deleting Case",
592 "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.",
593 "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...",
594 "Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or there is a problem with the coordination service."
595 })
598 if (null != currentCase) {
600 }
601 }
602
603 /*
604 * Set up either a GUI progress indicator without a cancel button (can't
605 * cancel deleting a case) or a logging progress indicator.
606 */
610 } else {
612 }
613 progressIndicator.
start(Bundle.Case_progressMessage_preparing());
614 try {
617 } else {
618 /*
619 * First, acquire an exclusive case directory lock. The case
620 * cannot be deleted if another node has it open.
621 */
622 progressIndicator.
progress(Bundle.Case_progressMessage_checkingForOtherUser());
624 assert (null != dirLock);
627 throw new CaseActionException(Bundle.Case_exceptionMessage_cannotGetLockToDeleteCase(), ex);
628 }
629 }
630 } finally {
631 progressIndicator.
finish();
632 }
633 }
634
645 @Messages({
646 "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
647 })
650 if (null != currentCase) {
651 try {
654 /*
655 * Notify the user and continue (the error has already been
656 * logged in closeCurrentCase.
657 */
659 }
660 }
661 try {
662 logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
663 newCurrentCase.
open(isNewCase);
664 currentCase = newCurrentCase;
665 logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
668 }
671 logger.log(Level.INFO, String.format(
"Cancelled opening %s (%s) in %s as the current case", newCurrentCase.
getDisplayName(), newCurrentCase.
getName(), newCurrentCase.
getCaseDirectory()));
//NON-NLS
672 throw ex;
674 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
675 throw ex;
676 }
677 }
678 }
679
689 /*
690 * Replace all non-ASCII characters.
691 */
692 String uniqueCaseName = caseDisplayName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
693
694 /*
695 * Replace all control characters.
696 */
697 uniqueCaseName = uniqueCaseName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
698
699 /*
700 * Replace /, ,円 :, ?, space, ' ".
701 */
702 uniqueCaseName = uniqueCaseName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
703
704 /*
705 * Make it all lowercase.
706 */
707 uniqueCaseName = uniqueCaseName.toLowerCase();
708
709 /*
710 * Add a time stamp for uniqueness.
711 */
712 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
713 Date date = new Date();
714 uniqueCaseName = uniqueCaseName + "_" + dateFormat.format(date);
715
716 return uniqueCaseName;
717 }
718
728
729 File caseDirF = new File(caseDir);
730
731 if (caseDirF.exists()) {
732 if (caseDirF.isFile()) {
734 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.existNotDir", caseDir));
735
736 } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
737 throw new CaseActionException(
738 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.existCantRW", caseDir));
739 }
740 }
741
742 try {
743 boolean result = (caseDirF).mkdirs(); // create root case Directory
744
745 if (result == false) {
746 throw new CaseActionException(
747 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.cantCreate", caseDir));
748 }
749
750 // create the folders inside the case directory
751 String hostClause = "";
752
754 hostClause = File.separator + NetworkUtils.getLocalHostName();
755 }
756 result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
757 && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
758 && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
759 && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
760
761 if (result == false) {
762 throw new CaseActionException(
763 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
764 }
765
766 final String modulesOutDir = caseDir + hostClause + File.separator +
MODULE_FOLDER;
767 result = new File(modulesOutDir).mkdir();
768
769 if (result == false) {
770 throw new CaseActionException(
771 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.cantCreateModDir",
772 modulesOutDir));
773 }
774
775 final String reportsOutDir = caseDir + hostClause + File.separator +
REPORTS_FOLDER;
776 result = new File(reportsOutDir).mkdir();
777
778 if (result == false) {
779 throw new CaseActionException(
780 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.cantCreateReportsDir",
781 modulesOutDir));
782
783 }
784
785 } catch (MissingResourceException | CaseActionException e) {
786 throw new CaseActionException(
787 NbBundle.getMessage(
Case.class,
"Case.createCaseDir.exception.gen", caseDir), e);
788 }
789 }
790
798 static Map<Long, String> getImagePaths(SleuthkitCase db) {
799 Map<Long, String> imgPaths = new HashMap<>();
800 try {
801 Map<Long, List<String>> imgPathsList = db.getImagePaths();
802 for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
803 if (entry.getValue().size() > 0) {
804 imgPaths.put(entry.getKey(), entry.getValue().get(0));
805 }
806 }
807 } catch (TskCoreException ex) {
808 logger.log(Level.SEVERE, "Error getting image paths", ex); //NON-NLS
809 }
810 return imgPaths;
811 }
812
829 @Messages({
830 "Case.progressMessage.deletingTextIndex=Deleting text index...",
831 "Case.progressMessage.deletingCaseDatabase=Deleting case database...",
832 "Case.progressMessage.deletingCaseDirectory=Deleting case directory...",
833 "Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details"
834 })
836 boolean errorsOccurred = false;
838 /*
839 * Delete the case database from the database server.
840 */
841 try {
842 progressIndicator.
progress(Bundle.Case_progressMessage_deletingCaseDatabase());
843 CaseDbConnectionInfo db;
845 Class.forName("org.postgresql.Driver"); //NON-NLS
846 try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
847 Statement statement = connection.createStatement();) {
848 String deleteCommand =
"DROP DATABASE \"" + metadata.
getCaseDatabaseName() +
"\"";
//NON-NLS
849 statement.execute(deleteCommand);
850 }
853 errorsOccurred = true;
854 }
855 }
856
857 /*
858 * Delete the text index.
859 */
860 progressIndicator.
progress(Bundle.Case_progressMessage_deletingTextIndex());
862 try {
863 searchService.deleteTextIndex(metadata);
866 errorsOccurred = true;
867 }
868 }
869
870 /*
871 * Delete the case directory.
872 */
873 progressIndicator.
progress(Bundle.Case_progressMessage_deletingCaseDirectory());
876 errorsOccurred = true;
877 }
878
879 /*
880 * If running in a GUI, remove the case from the Recent Cases menu
881 */
883 SwingUtilities.invokeLater(() -> {
884 RecentCases.getInstance().removeRecentCase(metadata.
getCaseDisplayName(), metadata.getFilePath().toString());
885 });
886 }
887
888 if (errorsOccurred) {
890 }
891 }
892
903 @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"})
905 try {
906 String resourcesNodeName = caseDir + "_resources";
908 if (null == lock) {
909 throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
910 }
911 return lock;
912 } catch (InterruptedException ex) {
915 throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
916 }
917 }
918
924 SwingUtilities.invokeLater(() -> {
925 /*
926 * If the case database was upgraded for a new schema and a
927 * backup database was created, notify the user.
928 */
930 String backupDbPath = caseDb.getBackupDatabasePath();
931 if (null != backupDbPath) {
932 JOptionPane.showMessageDialog(
933 mainFrame,
934 NbBundle.getMessage(
Case.class,
"Case.open.msgDlg.updated.msg", backupDbPath),
935 NbBundle.getMessage(
Case.class,
"Case.open.msgDlg.updated.title"),
936 JOptionPane.INFORMATION_MESSAGE);
937 }
938
939 /*
940 * Look for the files for the data sources listed in the case
941 * database and give the user the opportunity to locate any that
942 * are missing.
943 */
944 Map<Long, String> imgPaths = getImagePaths(caseDb);
945 for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
946 long obj_id = entry.getKey();
947 String path = entry.getValue();
949 if (!fileExists) {
950 int response = JOptionPane.showConfirmDialog(
951 mainFrame,
952 NbBundle.getMessage(
Case.class,
"Case.checkImgExist.confDlg.doesntExist.msg", path),
953 NbBundle.getMessage(
Case.class,
"Case.checkImgExist.confDlg.doesntExist.title"),
954 JOptionPane.YES_NO_OPTION);
955 if (response == JOptionPane.YES_OPTION) {
956 MissingImageDialog.makeDialog(obj_id, caseDb);
957 } else {
958 logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS
959
960 }
961 }
962 }
963
964 /*
965 * Enable the case-specific actions.
966 */
968 ).setEnabled(true);
969 CallableSystemAction
971 ).setEnabled(true);
972 CallableSystemAction
973 .get(CasePropertiesAction.class
974 ).setEnabled(true);
975 CallableSystemAction
976 .get(CaseDeleteAction.class
977 ).setEnabled(true);
978 CallableSystemAction
980 ).setEnabled(true);
981 CallableSystemAction
983 ).setEnabled(false);
984
985 /*
986 * Add the case to the recent cases tracker that supplies a list
987 * of recent cases to the recent cases menu item and the
988 * open/create case dialog.
989 */
990 RecentCases.getInstance().addRecentCase(newCurrentCase.
getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString());
991
992 /*
993 * Open the top components (windows within the main application
994 * window).
995 */
996 if (newCurrentCase.
hasData()) {
998 }
999
1000 /*
1001 * Reset the main window title to:
1002 *
1003 * [curent case display name] - [application name].
1004 */
1006 });
1007 }
1008 }
1009
1010 /*
1011 * Update the GUI to to reflect the lack of a current case.
1012 */
1015 SwingUtilities.invokeLater(() -> {
1016 /*
1017 * Close the top components (windows within the main application
1018 * window).
1019 */
1021
1022 /*
1023 * Disable the case-specific menu items.
1024 */
1025 CallableSystemAction
1027 ).setEnabled(false);
1028 CallableSystemAction
1030 ).setEnabled(false);
1031 CallableSystemAction
1032 .get(CasePropertiesAction.class
1033 ).setEnabled(false);
1034 CallableSystemAction
1035 .get(CaseDeleteAction.class
1036 ).setEnabled(false);
1037 CallableSystemAction
1039 ).setEnabled(false);
1040 CallableSystemAction
1042 ).setEnabled(false);
1043
1044 /*
1045 * Clear the notifications in the notfier component in the lower
1046 * right hand corner of the main application window.
1047 */
1049
1050 /*
1051 * Reset the main window title to be just the application name,
1052 * instead of [curent case display name] - [application name].
1053 */
1055 });
1056 }
1057 }
1058
1063 File tempFolder = new File(tempSubDirPath);
1064 if (tempFolder.isDirectory()) {
1065 File[] files = tempFolder.listFiles();
1066 if (files.length > 0) {
1067 for (File file : files) {
1068 if (file.isDirectory()) {
1070 } else {
1071 file.delete();
1072 }
1073 }
1074 }
1075 }
1076 }
1077
1085 }
1086
1094 }
1095
1103 }
1104
1111 return metadata.getCreatedDate();
1112 }
1113
1121 }
1122
1130 }
1131
1139 }
1140
1148 }
1149
1157 }
1158
1169 Path hostPath;
1172 } else {
1173 hostPath = Paths.get(caseDirectory);
1174 }
1175 if (!hostPath.toFile().exists()) {
1176 hostPath.toFile().mkdirs();
1177 }
1178 return hostPath.toString();
1179 }
1180
1189 }
1190
1199 }
1200
1209 }
1210
1219 }
1220
1229 }
1230
1239 }
1240
1251 return path.subpath(path.getNameCount() - 2, path.getNameCount()).toString();
1252 } else {
1253 return path.subpath(path.getNameCount() - 1, path.getNameCount()).toString();
1254 }
1255 }
1256
1267 List<Content> list = caseDb.getRootObjects();
1268 hasDataSources = (list.size() > 0);
1269 return list;
1270 }
1271
1278 Set<TimeZone> timezones = new HashSet<>();
1279 try {
1281 final Content dataSource = c.getDataSource();
1282 if ((dataSource != null) && (dataSource instanceof Image)) {
1283 Image image = (Image) dataSource;
1284 timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1285 }
1286 }
1287 } catch (TskCoreException ex) {
1288 logger.log(Level.SEVERE, "Error getting data source time zones", ex); //NON-NLS
1289 }
1290 return timezones;
1291 }
1292
1301 }
1302
1310 if (!hasDataSources) {
1311 try {
1313 } catch (TskCoreException ex) {
1314 logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
1315 }
1316 }
1318 }
1319
1332 }
1333
1346 }
1347
1361 }
1362
1372 }
1373
1383 }
1384
1394 }
1395
1405 }
1406
1418 public void addReport(String localPath, String srcModuleName, String reportName)
throws TskCoreException {
1419 String normalizedLocalPath;
1420 try {
1421 normalizedLocalPath = Paths.get(localPath).normalize().toString();
1422 } catch (InvalidPathException ex) {
1423 String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1424 throw new TskCoreException(errorMsg, ex);
1425 }
1426 Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName);
1428 }
1429
1439 return this.caseDb.getAllReports();
1440 }
1441
1450 public void deleteReports(Collection<? extends Report> reports)
throws TskCoreException {
1451 for (Report report : reports) {
1452 this.caseDb.deleteReport(report);
1454 }
1455 }
1456
1464 }
1465
1469 @Messages({
1470 "Case.exceptionMessage.metadataUpdateError=Failed to update case metadata, cannot change case display name."
1471 })
1472 void updateDisplayName(String newDisplayName) throws CaseActionException {
1474 try {
1475 metadata.setCaseDisplayName(newDisplayName);
1476 } catch (CaseMetadataException ex) {
1477 throw new CaseActionException(Bundle.Case_exceptionMessage_metadataUpdateError());
1478 }
1479 eventPublisher.
publish(
new AutopsyEvent(Events.NAME.toString(), oldDisplayName, newDisplayName));
1480 if (RuntimeProperties.runningWithGUI()) {
1481 SwingUtilities.invokeLater(() -> {
1482 mainFrame.setTitle(newDisplayName + " - " + UserPreferences.getAppName());
1483 try {
1484 RecentCases.getInstance().updateRecentCase(oldDisplayName, metadata.getFilePath().toString(), newDisplayName, metadata.getFilePath().toString());
1485 } catch (Exception ex) {
1486 logger.log(Level.SEVERE, "Error updating case name in UI", ex); //NON-NLS
1487 }
1488 });
1489 }
1490 }
1491
1506 private Case(
CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner) {
1508 }
1509
1516 metadata = caseMetaData;
1517 }
1518
1534 @Messages({
1535 "Case.progressIndicatorTitle.creatingCase=Creating Case",
1536 "Case.progressIndicatorTitle.openingCase=Opening Case",
1537 "Case.progressIndicatorCancelButton.label=Cancel",
1538 "Case.progressMessage.preparing=Preparing...",
1539 "Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
1540 "Case.progressMessage.cancelling=Cancelling...",
1541 "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
1542 "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
1543 })
1545 /*
1546 * Create and start either a GUI progress indicator with a Cancel button
1547 * or a logging progress indicator.
1548 */
1553 String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
1555 mainFrame,
1556 progressIndicatorTitle,
1557 new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1558 Bundle.Case_progressIndicatorCancelButton_label(),
1559 cancelButtonListener);
1560 } else {
1562 }
1563 progressIndicator.
start(Bundle.Case_progressMessage_preparing());
1564
1565 /*
1566 * Creating/opening a case is always done by creating a task running in
1567 * the same non-UI thread that will be used to close the case, so a
1568 * single-threaded executor service is created here and saved as case
1569 * state (must be volatile for cancellation to work).
1570 *
1571 * --- If the case is a single-user case, this supports cancelling
1572 * opening of the case by cancelling the task.
1573 *
1574 * --- If the case is a multi-user case, this still supports
1575 * cancellation, but it also makes it possible for the shared case
1576 * directory lock held as long as the case is open to be released in the
1577 * same thread in which it was acquired, as is required by the
1578 * coordination service.
1579 */
1581 caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
1582 Future<Void> future = caseLockingExecutor.submit(() -> {
1584 open(isNewCase, progressIndicator);
1585 } else {
1586 /*
1587 * First, acquire a shared case directory lock that will be held
1588 * as long as this node has this case open. This will prevent
1589 * deletion of the case by another node. Next, acquire an
1590 * exclusive case resources lock to ensure only one node at a
1591 * time can create/open/upgrade/close the case resources.
1592 */
1593 progressIndicator.
progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
1596 assert (null != resourcesLock);
1597 open(isNewCase, progressIndicator);
1600 throw ex;
1601 }
1602 }
1603 return null;
1604 });
1605 if (null != cancelButtonListener) {
1607 }
1608
1609 /*
1610 * Wait for the case creation/opening task to finish.
1611 */
1612 try {
1613 future.get();
1614 } catch (InterruptedException discarded) {
1615 /*
1616 * The thread this method is running in has been interrupted. Cancel
1617 * the create/open task, wait for it to finish, and shut down the
1618 * executor. This can be done safely because if the task is
1619 * completed with a cancellation condition, the case will have been
1620 * closed and the case directory lock released will have been
1621 * released.
1622 */
1623 if (null != cancelButtonListener) {
1625 } else {
1626 future.cancel(true);
1627 }
1629 } catch (CancellationException discarded) {
1630 /*
1631 * The create/open task has been cancelled. Wait for it to finish,
1632 * and shut down the executor. This can be done safely because if
1633 * the task is completed with a cancellation condition, the case
1634 * will have been closed and the case directory lock released will
1635 * have been released.
1636 */
1639 } catch (ExecutionException ex) {
1640 /*
1641 * The create/open task has thrown an exception. Wait for it to
1642 * finish, and shut down the executor. This can be done safely
1643 * because if the task is completed with an execution condition, the
1644 * case will have been closed and the case directory lock released
1645 * will have been released.
1646 */
1648 throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
1649 } finally {
1650 progressIndicator.
finish();
1651 }
1652 }
1653
1666 try {
1667 if (Thread.currentThread().isInterrupted()) {
1669 }
1670
1671 if (isNewCase) {
1673 } else {
1675 }
1676
1677 if (Thread.currentThread().isInterrupted()) {
1679 }
1680
1682
1683 if (Thread.currentThread().isInterrupted()) {
1685 }
1687 /*
1688 * Cancellation or failure. Clean up. The sleep is a little hack to
1689 * clear the interrupted flag for this thread if this is a
1690 * cancellation scenario, so that the clean up can run to completion
1691 * in this thread.
1692 */
1693 try {
1694 Thread.sleep(1);
1695 } catch (InterruptedException discarded) {
1696 }
1697 close(progressIndicator);
1698 throw ex;
1699 }
1700 }
1701
1712 @Messages({
1713 "Case.progressMessage.creatingCaseDirectory=Creating case directory...",
1714 "Case.progressMessage.creatingCaseDatabase=Creating case database...",
1715 "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}",
1716 "Case.exceptionMessage.couldNotCreateMetadataFile=Failed to create case metadata file."
1717 })
1719 /*
1720 * Create the case directory, if it does not already exist.
1721 *
1722 * TODO (JIRA-2180): Always create the case directory as part of the
1723 * case creation process.
1724 */
1726 progressIndicator.
progress(Bundle.Case_progressMessage_creatingCaseDirectory());
1728 }
1729
1730 /*
1731 * Create the case database.
1732 */
1733 progressIndicator.
progress(Bundle.Case_progressMessage_creatingCaseDatabase());
1734 try {
1736 /*
1737 * For single-user cases, the case database is a SQLite database
1738 * with a standard name, physically located in the root of the
1739 * case directory.
1740 */
1742 metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
1743 } else {
1744 /*
1745 * For multi-user cases, the case database is a PostgreSQL
1746 * database with a name derived from the case display name,
1747 * physically located on a database server.
1748 */
1750 metadata.setCaseDatabaseName(caseDb.getDatabaseName());
1751 }
1752 } catch (TskCoreException ex) {
1753 throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex);
1757 throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateMetadataFile(), ex);
1758 }
1759 }
1760
1771 @Messages({
1772 "Case.progressMessage.openingCaseDatabase=Opening case database...",
1773 "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database."
1774 })
1776 try {
1777 progressIndicator.
progress(Bundle.Case_progressMessage_openingCaseDatabase());
1780 caseDb = SleuthkitCase.openCase(Paths.get(metadata.
getCaseDirectory(), databaseName).toString());
1782 try {
1784
1787 "Case.databaseConnectionInfo.error.msg"), ex);
1788
1789 }
1790 } else {
1792 "Case.open.exception.multiUserCaseNotEnabled"));
1793 }
1794 } catch (TskCoreException ex) {
1795 throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(), ex);
1796 }
1797 }
1798
1807 @Messages({
1808 "Case.progressMessage.switchingLogDirectory=Switching log directory...",
1809 "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...",
1810 "Case.progressMessage.openingCaseLevelServices=Opening case-level services...",
1811 "Case.progressMessage.openingApplicationServiceResources=Opening application service case resources...",
1812 "Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...",})
1814 /*
1815 * Switch to writing to the application logs in the logs subdirectory of
1816 * the case directory.
1817 */
1818 progressIndicator.
progress(Bundle.Case_progressMessage_switchingLogDirectory());
1820 if (Thread.currentThread().isInterrupted()) {
1822 }
1823
1824 /*
1825 * Clear the temp subdirectory of the case directory.
1826 */
1827 progressIndicator.
progress(Bundle.Case_progressMessage_clearingTempDirectory());
1829 if (Thread.currentThread().isInterrupted()) {
1831 }
1832
1833 /*
1834 * Open the case-level services.
1835 */
1836 progressIndicator.
progress(Bundle.Case_progressMessage_openingCaseLevelServices());
1837 this.caseServices =
new Services(caseDb);
1838 if (Thread.currentThread().isInterrupted()) {
1840 }
1841
1842 /*
1843 * Allow any registered application services to open any resources
1844 * specific to this case.
1845 */
1846 progressIndicator.
progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
1848 if (Thread.currentThread().isInterrupted()) {
1850 }
1851
1852 /*
1853 * If this case is a multi-user case, set up for communication with
1854 * other nodes.
1855 */
1857 progressIndicator.
progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
1858 try {
1860 if (Thread.currentThread().isInterrupted()) {
1862 }
1863 collaborationMonitor =
new CollaborationMonitor(metadata.
getCaseName());
1865 /*
1866 * The collaboration monitor and event channel are not
1867 * essential. Log an error and notify the user, but do not
1868 * throw.
1869 */
1870 logger.log(Level.SEVERE, "Failed to setup network communications", ex); //NON-NLS
1873 NbBundle.getMessage(
Case.class,
"Case.CollaborationSetup.FailNotify.Title"),
1874 NbBundle.getMessage(
Case.class,
"Case.CollaborationSetup.FailNotify.ErrMsg")));
1875 }
1876 }
1877 }
1878 }
1879
1884 @NbBundle.Messages({
1885 "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.title={0} Opening Case Resources",
1886 "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...",
1887 "# {0} - service name", "Case.servicesException.notificationTitle={0} Error"
1888 })
1890 /*
1891 * Each service gets its own independently cancellable/interruptible
1892 * task, running in a named thread managed by an executor service, with
1893 * its own progress indicator. This allows for cancellation of the
1894 * opening of case resources for individual services. It also makes it
1895 * possible to ensure that each service task completes before the next
1896 * one starts by awaiting termination of the executor service.
1897 */
1899 /*
1900 * Create a progress indicator for the task and start the task. If
1901 * running with a GUI, the progress indicator will be a dialog box
1902 * with a Cancel button.
1903 */
1907 cancelButtonListener =
new CancelButtonListener(Bundle.Case_serviceOpenCaseResourcesProgressIndicator_cancellingMessage(service.getServiceName()));
1909 mainFrame,
1910 Bundle.Case_serviceOpenCaseResourcesProgressIndicator_title(service.getServiceName()),
1911 new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
1912 Bundle.Case_progressIndicatorCancelButton_label(),
1913 cancelButtonListener);
1914 } else {
1916 }
1917 progressIndicator.
start(Bundle.Case_progressMessage_preparing());
1919 String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
1920 threadNameSuffix = threadNameSuffix.toLowerCase();
1922 ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
1923 Future<Void> future = executor.submit(() -> {
1924 service.openCaseResources(context);
1925 return null;
1926 });
1927 if (null != cancelButtonListener) {
1930 }
1931
1932 /*
1933 * Wait for the task to either be completed or
1934 * cancelled/interrupted, or for the opening of the case to be
1935 * cancelled.
1936 */
1937 try {
1938 future.get();
1939 } catch (InterruptedException discarded) {
1940 /*
1941 * The parent create/open case task has been cancelled.
1942 */
1944 future.cancel(true);
1945 } catch (CancellationException discarded) {
1946 /*
1947 * The opening of case resources by the application service has
1948 * been cancelled, so the executor service has thrown. Note that
1949 * there is no guarantee the task itself has responded to the
1950 * cancellation request yet.
1951 */
1953 } catch (ExecutionException ex) {
1954 /*
1955 * An exception was thrown while executing the task. The
1956 * case-specific application service resources are not
1957 * essential. Log an error and notify the user if running the
1958 * desktop GUI, but do not throw.
1959 */
1960 Case.
logger.log(Level.SEVERE, String.format(
"%s failed to open case resources for %s", service.getServiceName(), this.
getDisplayName()), ex);
1962 SwingUtilities.invokeLater(() -> {
1964 });
1965 }
1966 } finally {
1967 /*
1968 * Shut down the executor service and wait for it to finish.
1969 * This ensures that the task has finished. Without this, it
1970 * would be possible to start the next task before the current
1971 * task responded to a cancellation request.
1972 */
1974 progressIndicator.
finish();
1975 }
1976
1977 if (Thread.currentThread().isInterrupted()) {
1979 }
1980 }
1981 }
1982
1987 /*
1988 * Set up either a GUI progress indicator without a Cancel button or a
1989 * logging progress indicator.
1990 */
1994 mainFrame,
1995 Bundle.Case_progressIndicatorTitle_closingCase());
1996 } else {
1998 }
1999 progressIndicator.
start(Bundle.Case_progressMessage_preparing());
2000
2001 /*
2002 * Closing a case is always done in the same non-UI thread that
2003 * opened/created the case. If the case is a multi-user case, this
2004 * ensures that case directory lock that is held as long as the case is
2005 * open is released in the same thread in which it was acquired, as is
2006 * required by the coordination service.
2007 */
2008 Future<Void> future = caseLockingExecutor.submit(() -> {
2010 close(progressIndicator);
2011 } else {
2012 /*
2013 * Acquire an exclusive case resources lock to ensure only one
2014 * node at a time can create/open/upgrade/close the case
2015 * resources.
2016 */
2017 progressIndicator.
progress(Bundle.Case_progressMessage_preparing());
2019 assert (null != resourcesLock);
2020 close(progressIndicator);
2021 } finally {
2022 /*
2023 * Always release the case directory lock that was acquired
2024 * when the case was opened.
2025 */
2027 }
2028 }
2029 return null;
2030 });
2031
2032 try {
2033 future.get();
2034 } catch (InterruptedException | CancellationException unused) {
2035 /*
2036 * The wait has been interrupted by interrupting the thread running
2037 * this method. Not allowing cancellation of case closing, so ignore
2038 * the interrupt. Likewsie, cancellation of the case closing task is
2039 * not supported.
2040 */
2041 } catch (ExecutionException ex) {
2042 throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
2043 } finally {
2045 progressIndicator.
finish();
2046 }
2047 }
2048
2054 @Messages({
2055 "Case.progressMessage.shuttingDownNetworkCommunications=Shutting down network communications...",
2056 "Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...",
2057 "Case.progressMessage.closingCaseLevelServices=Closing case-level services...",
2058 "Case.progressMessage.closingCaseDatabase=Closing case database..."
2059 })
2062
2063 /*
2064 * Stop sending/receiving case events to and from other nodes if this is
2065 * a multi-user case.
2066 */
2068 progressIndicator.
progress(Bundle.Case_progressMessage_shuttingDownNetworkCommunications());
2069 if (null != collaborationMonitor) {
2070 collaborationMonitor.shutdown();
2071 }
2073 }
2074
2075 /*
2076 * Allow all registered application services providers to close
2077 * resources related to the case.
2078 */
2079 progressIndicator.
progress(Bundle.Case_progressMessage_closingApplicationServiceResources());
2081
2082 /*
2083 * Close the case-level services.
2084 */
2085 if (null != caseServices) {
2086 progressIndicator.
progress(Bundle.Case_progressMessage_closingCaseLevelServices());
2087 try {
2088 this.caseServices.
close();
2089 } catch (IOException ex) {
2090 logger.log(Level.SEVERE, String.format(
"Error closing internal case services for %s at %s",
this.getName(), this.
getCaseDirectory()), ex);
2091 }
2092 }
2093
2094 /*
2095 * Close the case database
2096 */
2097 if (null != caseDb) {
2098 progressIndicator.
progress(Bundle.Case_progressMessage_closingCaseDatabase());
2099 caseDb.close();
2100 }
2101
2102 /*
2103 * Switch the log directory.
2104 */
2105 progressIndicator.
progress(Bundle.Case_progressMessage_switchingLogDirectory());
2107 }
2108
2113 @Messages({
2114 "# {0} - serviceName", "Case.serviceCloseResourcesProgressIndicator.title={0} Closing Case Resources",
2115 "# {0} - service name", "# {1} - exception message", "Case.servicesException.serviceResourcesCloseError=Could not close case resources for {0} service: {1}"
2116 })
2118 /*
2119 * Each service gets its own independently cancellable task, and thus
2120 * its own task progress indicator.
2121 */
2126 mainFrame,
2127 Bundle.Case_serviceCloseResourcesProgressIndicator_title(service.getServiceName()));
2128 } else {
2130 }
2131 progressIndicator.
start(Bundle.Case_progressMessage_preparing());
2133 String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS
2134 threadNameSuffix = threadNameSuffix.toLowerCase();
2136 ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
2137 Future<Void> future = executor.submit(() -> {
2138 service.closeCaseResources(context);
2139 return null;
2140 });
2141 try {
2142 future.get();
2143 } catch (InterruptedException ex) {
2144 Case.
logger.log(Level.SEVERE, String.format(
"Unexpected interrupt while waiting on %s service to close case resources", service.getServiceName()), ex);
2145 } catch (CancellationException ex) {
2146 Case.
logger.log(Level.SEVERE, String.format(
"Unexpected cancellation while waiting on %s service to close case resources", service.getServiceName()), ex);
2147 } catch (ExecutionException ex) {
2148 Case.
logger.log(Level.SEVERE, String.format(
"%s service failed to open case resources", service.getServiceName()), ex);
2151 Bundle.Case_servicesException_notificationTitle(service.getServiceName()),
2152 Bundle.Case_servicesException_serviceResourcesCloseError(service.getServiceName(), ex.getLocalizedMessage())));
2153 }
2154 } finally {
2156 progressIndicator.
finish();
2157 }
2158 }
2159 }
2160
2169 @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."})
2171 try {
2175 }
2177 throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex);
2178 }
2179 }
2180
2188 try {
2192 logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex);
2193 }
2194 }
2195 }
2196
2205 if (!subDirectory.exists()) {
2206 subDirectory.mkdirs();
2207 }
2208 return subDirectory.toString();
2209
2210 }
2211
2221 executor.shutdown();
2222 boolean taskCompleted = false;
2223 while (!taskCompleted) {
2224 try {
2225 taskCompleted = executor.awaitTermination(EXECUTOR_AWAIT_TIMEOUT_SECS, TimeUnit.SECONDS);
2226 } catch (InterruptedException ignored) {
2227 /*
2228 * The current policy is to wait for the task to finish so that
2229 * the case can be left in a consistent state.
2230 *
2231 * For a specific example of the motivation for this policy,
2232 * note that a application service (Solr search service)
2233 * experienced an error condition when opening case resources
2234 * that left the service blocked uninterruptibly on a socket
2235 * read. This eventually led to a mysterious "freeze" as the
2236 * user-cancelled service task continued to run holdiong a lock
2237 * that a UI thread soon tried to acquire. Thus it has been
2238 * deemed better to make the "freeze" happen in a more
2239 * informative way, i.e., with the progress indicator for the
2240 * unfinished task on the screen, if a similar error condition
2241 * arises again.
2242 */
2243 }
2244 }
2245 }
2246
2251 @ThreadSafe
2253
2255 @GuardedBy("this")
2257 @GuardedBy("this")
2259 @GuardedBy("this")
2261
2272 }
2273
2281 /*
2282 * If the cancel button has already been pressed, pass the
2283 * cancellation on to the case context.
2284 */
2287 }
2288 }
2289
2297 /*
2298 * If the cancel button has already been pressed, cancel the Future
2299 * of the task.
2300 */
2303 }
2304 }
2305
2311 @Override
2314 }
2315
2320 /*
2321 * At a minimum, set the cancellation requested flag of this
2322 * listener.
2323 */
2326 /*
2327 * Set the cancellation request flag and display the
2328 * cancellation message in the progress indicator for the case
2329 * context associated with this listener.
2330 */
2334 ((ModalDialogProgressIndicator) progressIndicator).setCancelling(cancellationMessage);
2335 }
2336 }
2338 }
2340 /*
2341 * Cancel the Future of the task associated with this listener.
2342 * Note that the task thread will be interrupted if the task is
2343 * blocked.
2344 */
2346 }
2347 }
2348 }
2349
2354
2356
2359 }
2360
2361 @Override
2363 return new Thread(task, threadName);
2364 }
2365
2366 }
2367
2375 @Deprecated
2378 }
2379
2399 @Deprecated
2402 }
2403
2424 @Deprecated
2427 }
2428
2440 @Deprecated
2443 }
2444
2454 @Deprecated
2457 }
2458
2464 @Deprecated
2467 }
2468
2482 @Deprecated
2485 }
2486
2496 @Deprecated
2498 return new File(filePath).isFile();
2499 }
2500
2509 @Deprecated
2512 }
2513
2521 @Deprecated
2524 }
2525
2535 @Deprecated
2537 return "ModuleOutput"; //NON-NLS
2538 }
2539
2549 @Deprecated
2550 public static PropertyChangeSupport
2552 return new PropertyChangeSupport(
Case.class
2553 );
2554 }
2555
2564 @Deprecated
2567 }
2568
2583 @Deprecated
2585 try {
2586 Image newDataSource = caseDb.getImageById(imgId);
2588 return newDataSource;
2589 } catch (TskCoreException ex) {
2590 throw new CaseActionException(NbBundle.getMessage(
this.getClass(),
"Case.addImg.exception.msg"), ex);
2591 }
2592 }
2593
2601 @Deprecated
2604 }
2605
2616 @Deprecated
2617 public void deleteReports(Collection<? extends Report> reports,
boolean deleteFromDisk)
throws TskCoreException {
2619 }
2620
2621 }
String getLogDirectoryPath()
static final AutopsyEventPublisher eventPublisher
List< Content > getDataSources()
String getModuleOutputDirectoryRelativePath()
void notifyContentTagDeleted(ContentTag deletedTag)
Case(CaseMetadata caseMetaData)
static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS
static CaseType fromString(String typeName)
static void shutDownTaskExecutor(ExecutorService executor)
static final String CASE_ACTION_THREAD_NAME
void publishLocally(AutopsyEvent event)
Case(CaseType caseType, String caseDir, String caseDisplayName, String caseNumber, String examiner)
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Set< TimeZone > getTimeZones()
Image addImage(String imgPath, long imgId, String timeZone)
static synchronized IngestManager getInstance()
static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir)
static boolean runningWithGUI
static void closeCurrentCase()
String getTempDirectory()
static boolean existsCurrentCase()
static void removePropertyChangeListener(PropertyChangeListener listener)
void start(String message, int totalWorkUnits)
static final Logger logger
void publish(AutopsyEvent event)
static final int RESOURCES_LOCK_TIMOUT_HOURS
String getLocalizedDisplayName()
ADDING_DATA_SOURCE_FAILED
static final String EXPORT_FOLDER
String getCaseDirectory()
static String getAppName()
static volatile Frame mainFrame
static String convertTimeZone(String timeZoneId)
static boolean driveExists(String path)
static void openCoreWindows()
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
synchronized static void setLogDirectory(String directoryPath)
TaskThreadFactory(String threadName)
static final String CACHE_FOLDER
static String getAutopsyVersion()
CaseType(String typeName)
String getReportDirectory()
void createCaseData(ProgressIndicator progressIndicator)
static boolean getIsMultiUserModeEnabled()
void addReport(String localPath, String srcModuleName, String reportName)
static void updateGUIForCaseOpened(Case newCurrentCase)
static CaseDbConnectionInfo getDatabaseConnectionInfo()
String getModulesOutputDirAbsPath()
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
void closeAppServiceCaseResources()
static final String SINGLE_USER_CASE_DB_NAME
static void deleteCase(CaseMetadata metadata)
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
synchronized void closeRemoteEventChannel()
volatile ExecutorService caseLockingExecutor
List< Report > getAllReports()
static void clearTempSubDir(String tempSubDirPath)
static boolean isValidName(String caseName)
void acquireSharedCaseDirLock(String caseDir)
CollaborationMonitor collaborationMonitor
void releaseSharedCaseDirLock(String caseDir)
static void closeCoreWindows()
ProgressIndicator getProgressIndicator()
static String getModulesOutputDirRelPath()
Set< TimeZone > getTimeZone()
void openCaseData(ProgressIndicator progressIndicator)
static final String MODULE_FOLDER
static void create(String caseDir, String caseDisplayName, String caseNumber, String examiner)
synchronized void openRemoteEventChannel(String channelName)
void openAppServiceCaseResources()
void openServices(ProgressIndicator progressIndicator)
static void invokeStartupDialog()
static String displayNameToUniqueName(String caseDisplayName)
void open(boolean isNewCase)
static void openAsCurrentCase(String caseMetadataFilePath)
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int DIR_LOCK_TIMOUT_HOURS
void close(ProgressIndicator progressIndicator)
SleuthkitCase getSleuthkitCase()
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
static PropertyChangeSupport getPropertyChangeSupport()
String getCacheDirectory()
static void addPropertyChangeListener(PropertyChangeListener listener)
static final long EXECUTOR_AWAIT_TIMEOUT_SECS
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
String getModuleDirectory()
Thread newThread(Runnable task)
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
final CaseMetadata metadata
void deleteReports(Collection<?extends Report > reports)
void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId)
static boolean pathExists(String filePath)
BLACKBOARD_ARTIFACT_TAG_ADDED
static void open(String caseMetadataFilePath)
static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase)
String getOutputDirectory()
static void error(String title, String message)
String getOrCreateSubdirectory(String subDirectoryName)
boolean equalsName(String otherTypeName)
static final String EVENT_CHANNEL_NAME
static Case getCurrentCase()
static String getLocalHostName()
synchronized static Logger getLogger(String name)
static String convertToAlphaNumericFormat(String timeZoneId)
static final String LOG_FOLDER
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static String getAppName()
static synchronized CoordinationService getInstance()
static volatile Case currentCase
static String getVersion()
String getExportDirectory()
static void updateGUIForCaseClosed()
void notifyAddingDataSource(UUID eventId)
CoordinationService.Lock caseDirLock
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
void notifyContentTagAdded(ContentTag newTag)
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
static final String CASE_RESOURCES_THREAD_NAME
static StartupWindowProvider getInstance()
static void deleteCurrentCase()
static boolean deleteDir(File dirPath)
static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
void notifyFailedAddingDataSource(UUID addingDataSourceEventId)
static final Object caseActionSerializationLock
void open(boolean isNewCase, ProgressIndicator progressIndicator)
static boolean isCaseOpen()
String getTextIndexName()
static final String REPORTS_FOLDER
void progress(String message)
static final String TEMP_FOLDER
BLACKBOARD_ARTIFACT_TAG_DELETED
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static void deleteCase(CaseMetadata metadata, ProgressIndicator progressIndicator)
static void error(String message)