diff --git a/.gitignore b/.gitignore index 418d944b1e..1400fb33be 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ !/CoreLibs/nbproject/project.xml !/CoreLibs/nbproject/project.properties +/Core/test/qa-functional/data/ + /KeywordSearch/release/ /KeywordSearch/build/ /KeywordSearch/dist/ diff --git a/.travis.yml b/.travis.yml index 90b51996f9..8cde465f7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,14 @@ before_install: install: - sudo apt-get install testdisk - cd sleuthkit/sleuthkit - - sh install-sleuthkit.sh -script: + - sh travis_build.sh +script: + - set -e + - echo "building autopsy..." && echo -en 'travis_fold:start:script.build\\r' - cd $TRAVIS_BUILD_DIR/ - - ant build + - ant -q build + - echo -en 'travis_fold:end:script.build\\r' + - echo "testing autopsy..." && echo -en 'travis_fold:start:script.tests\\r' - cd Core/ - - xvfb-run ant test - + - xvfb-run ant -q test + - echo -en 'travis_fold:end:script.tests\\r' diff --git a/Core/build.xml b/Core/build.xml index 3555ff0711..401d0dfb6b 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -82,16 +82,20 @@ verbose="true"/> - - - - - + + + + + + + + + - + diff --git a/Core/manifest.mf b/Core/manifest.mf index 260c73c542..67d3366e22 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -2,7 +2,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.core/10 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 22 +OpenIDE-Module-Implementation-Version: 23 OpenIDE-Module-Requires: org.openide.windows.WindowManager AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 02df4b01bb..7ae5484bc9 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -18,7 +18,20 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar -file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar +file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar +file.reference.bcprov-jdk15on-1.54.jar=release/modules/ext/bcprov-jdk15on-1.54.jar +file.reference.jackcess-2.1.8.jar=release/modules/ext/jackcess-2.1.8.jar +file.reference.jackcess-encrypt-2.1.2.jar=release/modules/ext/jackcess-encrypt-2.1.2.jar +file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar +file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0.1.jar +file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar +file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar +file.reference.cxf-rt-rs-client-3.0.16.jar=release/modules/ext/cxf-rt-rs-client-3.0.16.jar +file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar +file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar +file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar +file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar +file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar @@ -34,5 +47,5 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar -spec.version.base=10.10 +spec.version.base=10.11 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 0159ec9880..ac3ec60d44 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -327,6 +327,7 @@ org.sleuthkit.autopsy.events org.sleuthkit.autopsy.filesearch org.sleuthkit.autopsy.guiutils + org.sleuthkit.autopsy.healthmonitor org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions @@ -347,8 +348,8 @@ release/modules/ext/jdom-2.0.5.jar - ext/sleuthkit-postgresql-4.6.0.jar - release/modules/ext/sleuthkit-postgresql-4.6.0.jar + ext/sleuthkit-postgresql-4.6.1.jar + release/modules/ext/sleuthkit-postgresql-4.6.1.jar ext/opencv-248.jar @@ -410,10 +411,58 @@ ext/curator-client-2.8.0.jar release/modules/ext/curator-client-2.8.0.jar + + ext/bcprov-jdk15on-1.54.jar + release/modules/ext/bcprov-jdk15on-1.54.jar + + + ext/jackcess-2.1.8.jar + release/modules/ext/jackcess-2.1.8.jar + + + ext/jackcess-encrypt-2.1.2.jar + release/modules/ext/jackcess-encrypt-2.1.2.jar + + + ext/jempbox-1.8.13.jar + release/modules/ext/jempbox-1.8.13.jar + + + ext/javax.ws.rs-api-2.0.1.jar + release/modules/ext/javax.ws.rs-api-2.0.1.jar + + + ext/cxf-rt-rs-client-3.0.16.jar + release/modules/ext/cxf-rt-rs-client-3.0.16.jar + + + ext/cxf-rt-transports-http-3.0.16.jar + release/modules/ext/cxf-rt-transports-http-3.0.16.jar + + + ext/cxf-core-3.0.16.jar + release/modules/ext/cxf-core-3.0.16.jar + + + ext/cxf-rt-frontend-jaxrs-3.0.16.jar + release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar + ext/tika-parsers-1.17.jar release/modules/ext/tika-parsers-1.17.jar + + ext/fontbox-2.0.8.jar + release/modules/ext/fontbox-2.0.8.jar + + + ext/pdfbox-2.0.8.jar + release/modules/ext/pdfbox-2.0.8.jar + + + ext/pdfbox-tools-2.0.8.jar + release/modules/ext/pdfbox-tools-2.0.8.jar + ext/sqlite-jdbc-3.8.11.jar release/modules/ext/sqlite-jdbc-3.8.11.jar diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 47bd201ec1..7ad185ac8e 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -84,7 +84,7 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { new Thread(() -> { for (BlackboardArtifact artifact : selectedArtifacts) { try { - Case.getOpenCase().getServices().getTagsManager().addBlackboardArtifactTag(artifact, tagName, comment); + Case.getCurrentCaseThrows().getServices().getTagsManager().addBlackboardArtifactTag(artifact, tagName, comment); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(AddBlackboardArtifactTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); //NON-NLS SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java index 9e99922775..d6090b2ace 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java @@ -45,7 +45,7 @@ public class AddBookmarkTagAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { try { - Map tagNamesMap = Case.getOpenCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap(); + Map tagNamesMap = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap(); TagName bookmarkTagName = tagNamesMap.get(BOOKMARK); /* diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index dad777585f..602789735d 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -140,7 +140,7 @@ public class AddContentTagAction extends AddTagAction { } } - Case.getOpenCase().getServices().getTagsManager().addContentTag(file, tagName, comment); + Case.getCurrentCaseThrows().getServices().getTagsManager().addContentTag(file, tagName, comment); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); //NON-NLS AbstractFile fileCopy = file; diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 12e23bdb30..7d0212b51c 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -93,7 +93,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Get the current set of tag names. Map tagNamesMap = null; try { - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS @@ -170,7 +170,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { private void getAndAddTag(String tagDisplayName, TagName tagName, String comment) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java index 28aa03bed8..18ae9ac7e5 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java @@ -73,7 +73,7 @@ public class DeleteBlackboardArtifactTagAction extends AbstractAction { new Thread(() -> { for (BlackboardArtifactTag tag : selectedTags) { try { - Case.getOpenCase().getServices().getTagsManager().deleteBlackboardArtifactTag(tag); + Case.getCurrentCaseThrows().getServices().getTagsManager().deleteBlackboardArtifactTag(tag); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(DeleteBlackboardArtifactTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java index 065a023c2f..07cce02bfc 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java @@ -72,7 +72,7 @@ public class DeleteContentTagAction extends AbstractAction { new Thread(() -> { for (ContentTag tag : selectedTags) { try { - Case.getOpenCase().getServices().getTagsManager().deleteContentTag(tag); + Case.getCurrentCaseThrows().getServices().getTagsManager().deleteContentTag(tag); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(DeleteContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java index ce99fb1ec4..008cb419ff 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java @@ -98,7 +98,7 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem protected Void doInBackground() throws Exception { TagsManager tagsManager; try { - tagsManager = Case.getOpenCase().getServices().getTagsManager(); + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Error untagging artifact. No open case found.", ex); //NON-NLS Platform.runLater(() @@ -155,7 +155,7 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem Map tagNamesMap = null; try { // Get the current set of tag names. - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -168,7 +168,7 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem if (null != tagNamesMap && !tagNamesMap.isEmpty()) { try { List existingTagsList - = Case.getOpenCase().getServices().getTagsManager() + = Case.getCurrentCaseThrows().getServices().getTagsManager() .getBlackboardArtifactTagsByArtifact(artifact); for (Map.Entry entry : tagNamesMap.entrySet()) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java index abc316b33f..f336cd888f 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java @@ -98,7 +98,7 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen protected Void doInBackground() throws Exception { TagsManager tagsManager; try { - tagsManager = Case.getOpenCase().getServices().getTagsManager(); + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Error untagging file. No open case found.", ex); //NON-NLS Platform.runLater(() -> @@ -152,7 +152,7 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen Map tagNamesMap = null; try { // Get the current set of tag names. - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -165,7 +165,7 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen if (null != tagNamesMap && !tagNamesMap.isEmpty()) { try { List existingTagsList = - Case.getOpenCase().getServices().getTagsManager() + Case.getCurrentCaseThrows().getServices().getTagsManager() .getContentTagsByContent(file); for (Map.Entry entry : tagNamesMap.entrySet()) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index f295a613a5..51665012cd 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -139,7 +139,7 @@ public class GetTagNameAndCommentDialog extends JDialog { // Tag name DTOs may be null (user tag names that have not been used do // not exist in the database). try { - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesSet.addAll(tagsManager.getAllTagNames()); } catch (TskCoreException | NoCurrentCaseException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java index 4bdbf0bd29..21785f2003 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java @@ -110,7 +110,7 @@ public class GetTagNameDialog extends JDialog { // Get the current set of tag names and hash them for a speedy lookup in // case the user chooses an existing tag name from the tag names table. try { - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap.putAll(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(GetTagNameDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS @@ -348,7 +348,7 @@ public class GetTagNameDialog extends JDialog { if (tagName == null) { try { - tagName = Case.getOpenCase().getServices().getTagsManager().addTagName(tagDisplayName, userTagDescription, TagName.HTML_COLOR.NONE, status); + tagName = Case.getCurrentCaseThrows().getServices().getTagsManager().addTagName(tagDisplayName, userTagDescription, TagName.HTML_COLOR.NONE, status); dispose(); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS @@ -361,7 +361,7 @@ public class GetTagNameDialog extends JDialog { tagName = null; } catch (TagsManager.TagNameAlreadyExistsException ex) { try { - tagName = Case.getOpenCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName); + tagName = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName); } catch (TskCoreException | NoCurrentCaseException ex1) { Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " exists in database but an error occurred in retrieving it.", ex1); //NON-NLS JOptionPane.showMessageDialog(this, diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java index 021ecd6278..fb178c797d 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenLogFolderAction.java @@ -58,7 +58,7 @@ public final class OpenLogFolderAction implements ActionListener { /* * Open the log directory for the case. */ - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); logDir = new File(currentCase.getLogDirectoryPath()); } catch (NoCurrentCaseException ex) { /* diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index c79a559b2d..29b8d67960 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -57,7 +57,7 @@ public final class OpenOutputFolderAction extends CallableSystemAction { public void performAction() { File outputDir; try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); outputDir = new File(currentCase.getOutputDirectory()); if (outputDir.exists()) { try { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 21a7bca103..cbcb06c936 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -109,7 +109,7 @@ class AddImageTask implements Runnable { public void run() { Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java index 23a963bf6a..6c6097717e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java @@ -345,7 +345,7 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { new Thread(() -> { try { - Case.getOpenCase().notifyAddingDataSource(dataSourceId); + Case.getCurrentCaseThrows().notifyAddingDataSource(dataSourceId); } catch (NoCurrentCaseException ex) { Logger.getLogger(AddImageWizardAddingProgressVisual.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS } @@ -417,9 +417,9 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { new Thread(() -> { try { if (!contents.isEmpty()) { - Case.getOpenCase().notifyDataSourceAdded(contents.get(0), dataSourceId); + Case.getCurrentCaseThrows().notifyDataSourceAdded(contents.get(0), dataSourceId); } else { - Case.getOpenCase().notifyFailedAddingDataSource(dataSourceId); + Case.getCurrentCaseThrows().notifyFailedAddingDataSource(dataSourceId); } } catch (NoCurrentCaseException ex) { Logger.getLogger(AddImageWizardAddingProgressVisual.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index e01773a79c..43bfed1caa 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -59,7 +59,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { selectedDsp = lastDspUsed; //if the last selected DSP was the Local Disk DSP and it would be disabled then we want to select a different DSP try { - if ((Case.getOpenCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && selectedDsp.equals(LocalDiskDSProcessor.getType())) { + if ((Case.getCurrentCaseThrows().getCaseType() == Case.CaseType.MULTI_USER_CASE) && selectedDsp.equals(LocalDiskDSProcessor.getType())) { selectedDsp = ImageDSProcessor.getType(); } createDataSourceProcessorButtons(); @@ -131,7 +131,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); - if ((Case.getOpenCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())){ + if ((Case.getCurrentCaseThrows().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())){ dspButton.setEnabled(false); //disable the button for local disk DSP when this is a multi user case dspButton.setSelected(false); shouldAddMultiUserWarning = true; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java index e7e3702600..feae1ddb6b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java @@ -87,7 +87,7 @@ class AddLocalFilesTask implements Runnable { List errors = new ArrayList<>(); try { progress.setIndeterminate(true); - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); LocalFilesDataSource newDataSource = fileManager.addLocalFilesDataSource(deviceId, rootVirtualDirectoryName, "", localFilePaths, new ProgressUpdater()); newDataSources.add(newDataSource); } catch (TskDataException | TskCoreException | NoCurrentCaseException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index ab28defc01..81f497214a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -77,6 +77,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; +import org.sleuthkit.autopsy.commonfilesearch.CommonFilesSearchAction; import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; @@ -600,37 +601,41 @@ public class Case { } /** - * Deprecated. Use getOpenCase() instead. - * - * Gets the current case, if there is one, at the time of the call. + * Gets the current case. This method should only be called by clients that + * can be sure a case is currently open. Some examples of suitable clients + * are data source processors, ingest modules, and report modules. * * @return The current case. - * - * @throws IllegalStateException if there is no current case. - * - * @deprecated. Use getOpenCase() instead. */ - @Deprecated public static Case getCurrentCase() { - /* - * Throwing an unchecked exception is a bad idea here. - * - */ try { - return getOpenCase(); + return getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { + /* + * Throw a runtime exception, since this is a programming error. + */ throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"), ex); } } /** - * Gets the current open case, if there is one, at the time of the call. + * Gets the current case, if there is one, or throws an exception if there + * is no current case. This method should only be called by methods known to + * run in threads where it is possible that another thread has closed the + * current case. The exception provides some protection from the + * consequences of the race condition between the calling thread and a case + * closing thread, but it is not fool-proof. Background threads calling this + * method should put all operations in an exception firewall with a try and + * catch-all block to handle the possibility of bad timing. * - * @return The open case. + * TODO (JIRA-3825): Introduce a reference counting scheme for this get case + * method. * - * @throws NoCurrentCaseException if there is no open case. + * @return The current case. + * + * @throws NoCurrentCaseException if there is no current case. */ - public static Case getOpenCase() throws NoCurrentCaseException { + public static Case getCurrentCaseThrows() throws NoCurrentCaseException { Case openCase = currentCase; if (openCase == null) { throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen")); @@ -1097,6 +1102,7 @@ public class Case { CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); + CallableSystemAction.get(CommonFilesSearchAction.class).setEnabled(true); CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); /* @@ -1146,6 +1152,7 @@ public class Case { CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false); CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + CallableSystemAction.get(CommonFilesSearchAction.class).setEnabled(false); /* * Clear the notifications in the notfier component in the lower @@ -1560,8 +1567,8 @@ public class Case { * @throws TskCoreException if there is a problem adding the report to the * case database. */ - public Report addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { - return addReport(localPath, srcModuleName, reportName, null); + public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { + addReport(localPath, srcModuleName, reportName, null); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java index 2b333e2bdc..a389aca979 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java @@ -66,7 +66,7 @@ final class CaseDeleteAction extends CallableSystemAction { "# {0} - exception message", "Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}",}) public void actionPerformed(ActionEvent e) { try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); String caseName = currentCase.getName(); String caseDirectory = currentCase.getCaseDirectory(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index 76c280f6b2..b8542552c4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -54,7 +54,7 @@ class CaseInformationPanel extends javax.swing.JPanel { }) private void customizeComponents() { try { - propertiesPanel = new CasePropertiesPanel(Case.getOpenCase()); + propertiesPanel = new CasePropertiesPanel(Case.getCurrentCaseThrows()); } catch (NoCurrentCaseException ex) { Logger.getLogger(CaseInformationPanel.class.getName()).log(Level.INFO, "Exception while getting open case.", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java index c020e7b033..cc3c682830 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java @@ -50,7 +50,7 @@ final class CasePropertiesPanel extends javax.swing.JPanel { void updateCaseInfo() { try { - theCase = Case.getOpenCase(); + theCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); return; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index be7905e010..2db469e371 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -264,7 +264,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour try { // verify that the image has a file system that TSK can process - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); if (!DataSourceUtils.imageHasFileSystem(dataSourcePath)) { // image does not have a file system that TSK can process return 0; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 6fa73b7ff7..56d5e67839 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -319,7 +319,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { // Display warning if there is one (but don't disable "next" button) try { - if (false == PathValidator.isValid(path, Case.getOpenCase().getCaseType())) { + if (false == PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { pathErrorLabel.setVisible(true); pathErrorLabel.setText(Bundle.ImageFilePanel_pathValidation_dataSourceOnCDriveError()); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java index e0b1ec8f01..9bce28753f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java @@ -75,7 +75,7 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { private void refresh() { try { - SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); List ingestJobs = skCase.getIngestJobs(); this.ingestJobs = ingestJobs; this.repaint(); @@ -115,7 +115,7 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { IngestJobInfo currIngestJob = ingestJobs.get(rowIndex); if (columnIndex == 0) { try { - SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); return skCase.getContentById(currIngestJob.getObjectId()).getName(); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Failed to get content from db", ex); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index b22f858b63..fc702076a0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -382,7 +382,7 @@ final class LocalDiskPanel extends JPanel { } private static String getDefaultImageWriterFolder() throws NoCurrentCaseException { - return Paths.get(Case.getOpenCase().getModuleDirectory(), "Image Writer").toString(); + return Paths.get(Case.getCurrentCaseThrows().getModuleDirectory(), "Image Writer").toString(); } private void setPotentialImageWriterPath(LocalDisk disk) throws NoCurrentCaseException { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java index f98e8264ac..c7fa2c3de3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java @@ -197,7 +197,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat command.add("-f"); command.add("files"); command.add("-t"); - File l01Dir = new File(Case.getOpenCase().getModuleDirectory(), L01_EXTRACTION_DIR); //WJS-TODO change to getOpenCase() when that method exists + File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR); //WJS-TODO change to getOpenCase() when that method exists if (!l01Dir.exists()) { l01Dir.mkdirs(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index ecfafd7844..3ab4088094 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -283,7 +283,7 @@ final class LocalFilesPanel extends javax.swing.JPanel { errorLabel.setVisible(false); try { - final Case.CaseType currentCaseType = Case.getOpenCase().getCaseType(); + final Case.CaseType currentCaseType = Case.getCurrentCaseThrows().getCaseType(); for (String currentPath : pathsList) { if (!PathValidator.isValid(currentPath, currentCaseType)) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java index 4e70d4b248..d38106a0ab 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java @@ -191,7 +191,7 @@ final class LogicalEvidenceFilePanel extends javax.swing.JPanel implements Docum } // display warning if there is one (but don't disable "next" button) try { - if (!PathValidator.isValid(path, Case.getOpenCase().getCaseType())) { + if (!PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.LogicalEvidenceFilePanel_pathValidation_dataSourceOnCDriveError()); return false; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index fa125e6a32..1efbffe190 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -115,19 +115,19 @@ final class MultiUserNode extends AbstractNode { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), + sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), Bundle.CaseNode_column_name(), caseName)); - ss.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), + sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), caseCreatedDate)); - ss.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), + sheetSet.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), caseMetadataFilePath)); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 2f806bcecc..2b85a74333 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -91,9 +91,9 @@ final class NewCaseWizardAction extends CallableSystemAction { if (EamDb.isEnabled()) { //if the eam is enabled we need to save the case organization information now EamDb dbManager = EamDb.getInstance(); if (dbManager != null) { - CorrelationCase cRCase = dbManager.getCase(Case.getOpenCase()); + CorrelationCase cRCase = dbManager.getCase(Case.getCurrentCaseThrows()); if (cRCase == null) { - cRCase = dbManager.newCase(Case.getOpenCase()); + cRCase = dbManager.newCase(Case.getCurrentCaseThrows()); } if (!organizationName.isEmpty()) { for (EamOrganization org : dbManager.getOrganizations()) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java index 0dae889f5b..4625727d94 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java @@ -64,7 +64,7 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { if (editCurrentCase) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); return; @@ -94,7 +94,7 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { private void setUpOrganizationData() { if (EamDb.isEnabled()) { try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); if (currentCase != null) { EamDb dbManager = EamDb.getInstance(); selectedOrg = dbManager.getCase(currentCase).getOrg(); @@ -567,7 +567,7 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { private void updateCaseDetails() throws NoCurrentCaseException { if (caseDisplayNameTextField.isVisible()) { try { - Case.getOpenCase().updateCaseDetails(new CaseDetails( + Case.getCurrentCaseThrows().updateCaseDetails(new CaseDetails( caseDisplayNameTextField.getText(), caseNumberTextField.getText(), examinerTextField.getText(), tfExaminerPhoneText.getText(), tfExaminerEmailText.getText(), taNotesText.getText())); @@ -586,7 +586,7 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { if (EamDb.isEnabled()) { try { EamDb dbManager = EamDb.getInstance(); - CorrelationCase correlationCase = dbManager.getCase(Case.getOpenCase()); + CorrelationCase correlationCase = dbManager.getCase(Case.getCurrentCaseThrows()); if (caseDisplayNameTextField.isVisible()) { correlationCase.setDisplayName(caseDisplayNameTextField.getText()); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java index 6aeb3338d4..ccf592ca67 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/RecentCases.java @@ -374,7 +374,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { int i = 0; String currentCaseName = null; try { - currentCaseName = Case.getOpenCase().getDisplayName(); + currentCaseName = Case.getCurrentCaseThrows().getDisplayName(); } catch (NoCurrentCaseException ex) { // in case there is no current case. } @@ -407,7 +407,7 @@ final class RecentCases extends CallableSystemAction implements Presenter.Menu { String[] casePaths = new String[LENGTH]; String currentCasePath = null; try { - currentCasePath = Case.getOpenCase().getMetadata().getFilePath().toString(); + currentCasePath = Case.getCurrentCaseThrows().getMetadata().getFilePath().toString(); } catch (NoCurrentCaseException ex) { /* * There may be no current case. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java index ad08e643fd..8c2a2d6dfa 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java @@ -47,6 +47,6 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent implements S * @throws TskCoreException */ ContentTag getTagByID() throws NoCurrentCaseException, TskCoreException { - return Case.getOpenCase().getServices().getTagsManager().getContentTagByTagID(getTagID()); + return Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagByTagID(getTagID()); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java index dcf575a5dc..84a99ee8b2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/DataSourceAddedEvent.java @@ -79,7 +79,7 @@ public final class DataSourceAddedEvent extends AutopsyEvent implements Serializ } try { long id = (Long) super.getNewValue(); - dataSource = Case.getOpenCase().getSleuthkitCase().getContentById(id); + dataSource = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(id); return dataSource; } catch (NoCurrentCaseException | TskCoreException ex) { logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java index 2fe152d32e..7f35c625c5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java @@ -70,7 +70,7 @@ public final class ReportAddedEvent extends AutopsyEvent implements Serializable } try { long id = (Long) super.getNewValue(); - List reports = Case.getOpenCase().getSleuthkitCase().getAllReports(); + List reports = Case.getCurrentCaseThrows().getSleuthkitCase().getAllReports(); for (Report thisReport : reports) { if (thisReport.getId() == id) { report = thisReport; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java index 7ab639f06b..292383980b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java @@ -1,14 +1,14 @@ /* - * Sleuth Kit Data Model + * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java index 5c978f6378..fb02e7d2d4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java @@ -297,7 +297,7 @@ final class TagNameDefinition implements Comparable { } setting.append(tagName.toSettingsFormat()); try { - SleuthkitCase caseDb = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase caseDb = Case.getCurrentCaseThrows().getSleuthkitCase(); tagName.saveToCase(caseDb); } catch (NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java index 5073f4110b..419a92e8c9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java @@ -425,7 +425,7 @@ final class TagOptionsPanel extends javax.swing.JPanel implements OptionsPanel { for (String modifiedTagDisplayName : updatedStatusTags) { //if user closes their case after options have been changed but before application of them is complete don't notify try { - Case.getOpenCase().notifyTagDefinitionChanged(modifiedTagDisplayName); + Case.getCurrentCaseThrows().notifyTagDefinitionChanged(modifiedTagDisplayName); } catch (NoCurrentCaseException ex) { Logger.getLogger(TagOptionsPanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index b38165ef5c..f113a622fe 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -99,7 +99,7 @@ public class TagsManager implements Closeable { tagDisplayNames.add(tagType.getDisplayName()); }); try { - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); for (TagName tagName : tagsManager.getAllTagNames()) { tagDisplayNames.add(tagName.getDisplayName()); } @@ -340,7 +340,7 @@ public class TagsManager implements Closeable { ContentTag tag; tag = caseDb.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); try { - Case.getOpenCase().notifyContentTagAdded(tag); + Case.getCurrentCaseThrows().notifyContentTagAdded(tag); } catch (NoCurrentCaseException ex) { throw new TskCoreException("Added a tag to a closed case", ex); } @@ -358,7 +358,7 @@ public class TagsManager implements Closeable { public void deleteContentTag(ContentTag tag) throws TskCoreException { caseDb.deleteContentTag(tag); try { - Case.getOpenCase().notifyContentTagDeleted(tag); + Case.getCurrentCaseThrows().notifyContentTagDeleted(tag); } catch (NoCurrentCaseException ex) { throw new TskCoreException("Deleted a tag from a closed case", ex); } @@ -470,7 +470,7 @@ public class TagsManager implements Closeable { public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { BlackboardArtifactTag tag = caseDb.addBlackboardArtifactTag(artifact, tagName, comment); try { - Case.getOpenCase().notifyBlackBoardArtifactTagAdded(tag); + Case.getCurrentCaseThrows().notifyBlackBoardArtifactTagAdded(tag); } catch (NoCurrentCaseException ex) { throw new TskCoreException("Added a tag to a closed case", ex); } @@ -488,7 +488,7 @@ public class TagsManager implements Closeable { public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { caseDb.deleteBlackboardArtifactTag(tag); try { - Case.getOpenCase().notifyBlackBoardArtifactTagDeleted(tag); + Case.getCurrentCaseThrows().notifyBlackBoardArtifactTagDeleted(tag); } catch (NoCurrentCaseException ex) { throw new TskCoreException("Deleted a tag from a closed case", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index c16d16d70c..2a86e7c279 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -169,7 +169,7 @@ public class DataContentViewerOtherCases extends javax.swing.JPanel implements D private void showCaseDetails(int selectedRowViewIdx) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, Bundle.DataContentViewerOtherCases_noOpenCase_errMsg(), @@ -225,7 +225,7 @@ public class DataContentViewerOtherCases extends javax.swing.JPanel implements D if (0 != otherCasesTable.getSelectedRowCount()) { Calendar now = Calendar.getInstance(); String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now); - CSVFileChooser.setCurrentDirectory(new File(Case.getOpenCase().getExportDirectory())); + CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory())); CSVFileChooser.setSelectedFile(new File(fileName)); CSVFileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); @@ -434,7 +434,7 @@ public class DataContentViewerOtherCases extends javax.swing.JPanel implements D private Collection getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { // @@@ Check exception try { - String caseUUID = Case.getOpenCase().getName(); + String caseUUID = Case.getCurrentCaseThrows().getName(); EamDb dbManager = EamDb.getInstance(); Collection artifactInstances = dbManager.getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).stream() .filter(artifactInstance -> !artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) @@ -491,7 +491,7 @@ public class DataContentViewerOtherCases extends javax.swing.JPanel implements D if (af != null) { Content dataSource = af.getDataSource(); dataSourceName = dataSource.getName(); - deviceId = Case.getOpenCase().getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); + deviceId = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); } } catch (TskException | NoCurrentCaseException ex) { // do nothing. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index cba529954e..864bf50923 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -74,7 +74,7 @@ public class CorrelationDataSource implements Serializable { public static CorrelationDataSource fromTSKDataSource(CorrelationCase correlationCase, Content dataSource) throws EamDbException { Case curCase; try { - curCase = Case.getOpenCase(); + curCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { throw new EamDbException("Autopsy case is closed"); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index 43812eeebf..69cad2abef 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -90,7 +90,7 @@ public class EamArtifactUtil { // if they asked for it, add the instance details associated with this occurance. if (!eamArtifacts.isEmpty() && addInstanceDetails) { try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); AbstractFile bbSourceFile = currentCase.getSleuthkitCase().getAbstractFileById(bbArtifact.getObjectID()); if (null == bbSourceFile) { //@@@ Log this @@ -98,9 +98,9 @@ public class EamArtifactUtil { } // make an instance for the BB source file - CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getOpenCase()); + CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows()); if (null == correlationCase) { - correlationCase = EamDb.getInstance().newCase(Case.getOpenCase()); + correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows()); } CorrelationAttributeInstance eamInstance = new CorrelationAttributeInstance( correlationCase, @@ -146,7 +146,7 @@ public class EamArtifactUtil { // Get the associated artifact BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); if (attribute != null) { - BlackboardArtifact associatedArtifact = Case.getOpenCase().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); + BlackboardArtifact associatedArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); return EamArtifactUtil.getCorrelationAttributeFromBlackboardArtifact(correlationType, associatedArtifact); } @@ -254,9 +254,9 @@ public class EamArtifactUtil { try { CorrelationAttribute.Type filesType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); eamArtifact = new CorrelationAttribute(filesType, af.getMd5Hash()); - CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getOpenCase()); + CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows()); if (null == correlationCase) { - correlationCase = EamDb.getInstance().newCase(Case.getOpenCase()); + correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows()); } CorrelationAttributeInstance cei = new CorrelationAttributeInstance( correlationCase, diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index da11671a08..570f5bca06 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -163,8 +163,8 @@ final class CaseEventListener implements PropertyChangeListener { try { // Get the remaining tags on the content object - Content content = Case.getOpenCase().getSleuthkitCase().getContentById(contentID); - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(contentID); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); List tags = tagsManager.getContentTagsByContent(content); if (tags.stream() @@ -244,7 +244,7 @@ final class CaseEventListener implements PropertyChangeListener { } else { //BLACKBOARD_ARTIFACT_TAG_DELETED Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); return; @@ -327,10 +327,10 @@ final class CaseEventListener implements PropertyChangeListener { * that are tagged with the given tag name. */ try { - TagName tagName = Case.getOpenCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(modifiedTagName); + TagName tagName = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(modifiedTagName); //First update the artifacts //Get all BlackboardArtifactTags with this tag name - List artifactTags = Case.getOpenCase().getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); + List artifactTags = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); for (BlackboardArtifactTag bbTag : artifactTags) { //start with assumption that none of the other tags applied to this Correlation Attribute will prevent it's status from being changed boolean hasTagWithConflictingKnownStatus = false; @@ -346,7 +346,7 @@ final class CaseEventListener implements PropertyChangeListener { } //Get the BlackboardArtifact which this BlackboardArtifactTag has been applied to. BlackboardArtifact bbArtifact = bbTag.getArtifact(); - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); List tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact); //get all tags which are on this blackboard artifact for (BlackboardArtifactTag t : tags) { @@ -374,7 +374,7 @@ final class CaseEventListener implements PropertyChangeListener { } // Next update the files - List fileTags = Case.getOpenCase().getSleuthkitCase().getContentTagsByTagName(tagName); + List fileTags = Case.getCurrentCaseThrows().getSleuthkitCase().getContentTagsByTagName(tagName); //Get all ContentTags with this tag name for (ContentTag contentTag : fileTags) { //start with assumption that none of the other tags applied to this ContentTag will prevent it's status from being changed @@ -384,7 +384,7 @@ final class CaseEventListener implements PropertyChangeListener { // the status of the file in the central repository if (tagName.getKnownStatus() == TskData.FileKnown.UNKNOWN) { Content content = contentTag.getContent(); - TagsManager tagsManager = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); List tags = tagsManager.getContentTagsByContent(content); //get all tags which are on this file for (ContentTag t : tags) { @@ -436,7 +436,7 @@ final class CaseEventListener implements PropertyChangeListener { } Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); return; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index a7c267c959..9dc784e0d0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -163,7 +163,7 @@ public class IngestEventsListener { tifArtifact.addAttributes(attributes); try { // index the artifact for keyword search - Blackboard blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); blackboard.publishArtifact(tifArtifact); } catch (Blackboard.BlackboardException | NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + tifArtifact.getArtifactID(), ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java index 0575905b37..76f51f3c55 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java @@ -94,7 +94,7 @@ final class IngestModule implements FileIngestModule { } try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return ProcessResult.ERROR; @@ -233,7 +233,7 @@ final class IngestModule implements FileIngestModule { } Case autopsyCase; try { - autopsyCase = Case.getOpenCase(); + autopsyCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); throw new IngestModuleException("Exception while getting open case.", ex); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java index 6ef03ae00d..2f732ac60f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java @@ -68,7 +68,17 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter { @Override public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { - return new IngestModule((IngestSettings) settings); + if (settings instanceof IngestSettings) { + return new IngestModule((IngestSettings) settings); + } + /* + * Compatibility check for older versions. + */ + if (settings instanceof NoIngestModuleIngestJobSettings) { + return new IngestModule(new IngestSettings()); + } + + throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings"); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java new file mode 100644 index 0000000000..437d4d1885 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java @@ -0,0 +1,56 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.util.Map; + +/** + * Provides logic for selecting common files from all data sources. + */ +final class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadataBuilder { + + private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where (known != 1 OR known IS NULL)%s GROUP BY md5 HAVING COUNT(*) > 1) order by md5"; //NON-NLS + + /** + * Implements the algorithm for getting common files across all data + * sources. + * + * @param dataSourceIdMap a map of obj_id to datasource name + * @param filterByMediaMimeType match only on files whose mime types can be broadly categorized as media types + * @param filterByDocMimeType match only on files whose mime types can be broadly categorized as document types + */ + AllDataSourcesCommonFilesAlgorithm(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + + } + + @Override + protected String buildSqlSelectStatement() { + Object[] args = new String[]{SELECT_PREFIX, determineMimeTypeFilter()}; + return String.format(WHERE_CLAUSE, args); + } + + @Override + protected String buildTabTitle() { + final String buildCategorySelectionString = this.buildCategorySelectionString(); + final String titleTemplate = Bundle.CommonFilesMetadataBuilder_buildTabTitle_titleAll(); + return String.format(titleTemplate, new Object[]{buildCategorySelectionString}); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties new file mode 100644 index 0000000000..72ddffb748 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -0,0 +1,15 @@ +CommonFilesPanel.searchButton.text=Search +CommonFilesPanel.withinDataSourceRadioButton.text=At least one instance of a given MD5 must appear in the data source selected below: +CommonFilesPanel.allDataSourcesRadioButton.text=Files can be in any data source +CommonFilesPanel.cancelButton.text=Cancel +CommonFilesPanel.cancelButton.actionCommand=Cancel +CommonFilesPanel.selectedFileCategoriesButton.text=Match on the following file categories: +CommonFilesPanel.selectedFileCategoriesButton.toolTipText=Select from the options below... +CommonFilesPanel.pictureVideoCheckbox.text=Pictures and Videos +CommonFilesPanel.documentsCheckbox.text=Documents +CommonFilesPanel.commonFilesSearchLabel.text=Find duplicate files in the current case. +CommonFilesPanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... +CommonFilesPanel.allFileCategoriesRadioButton.text=Match on all file types +CommonFilesPanel.text=Indicate which data sources to consider while searching for duplicates: +CommonFilesPanel.categoriesLabel.text=Indicate which file types to include in results: +CommonFilesPanel.errorText.text=In order to search, you must select a file category. diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.form new file mode 100644 index 0000000000..7a6e3dabe5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.form @@ -0,0 +1,51 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.java new file mode 100644 index 0000000000..204b3136a9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.java @@ -0,0 +1,94 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; + +/** + * Dialog box for configuring and running common files search. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public final class CommonFilesDialog extends javax.swing.JDialog { + + private static final long serialVersionUID = 1L; + + /** + * Creates new form CommonFilesDialog + */ + @NbBundle.Messages({ + "CommonFilesDialog.frame.title=Find Common Files", + "CommonFilesDialog.frame.msg=Find Common Files"}) + public CommonFilesDialog() { + super(new JFrame(Bundle.CommonFilesDialog_frame_title()), + Bundle.CommonFilesDialog_frame_msg(), true); + initComponents(); + + this.setResizable(false); + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + commonFilesPanel1 = new org.sleuthkit.autopsy.commonfilesearch.CommonFilesPanel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setSize(new java.awt.Dimension(340, 320)); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowClosed(java.awt.event.WindowEvent evt) { + formWindowClosed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(commonFilesPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(commonFilesPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + ); + + pack(); + setLocationRelativeTo(null); + }// //GEN-END:initComponents + + private void formWindowClosed(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosed + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_formWindowClosed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.commonfilesearch.CommonFilesPanel commonFilesPanel1; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java new file mode 100644 index 0000000000..c7c70676b8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java @@ -0,0 +1,71 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.util.Collections; +import java.util.Map; + +/** + * Utility and wrapper model around data required for Common Files Search + * results. Subclass this to implement different selections of files from the + * case. + */ +final class CommonFilesMetadata { + + private final Map metadata; + + /** + * Create meta dat object which can be handed off to the node factories + * + * @param metadata map of md5 to parent-level node meta data + */ + CommonFilesMetadata(Map metadata) { + this.metadata = metadata; + } + + /** + * Find the meta data for the given md5. + * + * This is a convenience method - you can also iterate over + * getMetadata(). + * + * @param md5 key + * @return + */ + Md5Metadata getMetadataForMd5(String md5) { + return this.metadata.get(md5); + } + + Map getMetadata() { + return Collections.unmodifiableMap(this.metadata); + } + + /** + * How many distinct file instances exist for this metadata? + * @return number of file instances + */ + int size() { + int count = 0; + for (Md5Metadata data : this.metadata.values()) { + count += data.size(); + } + return count; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java new file mode 100644 index 0000000000..339763fd4e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java @@ -0,0 +1,261 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.HashUtility; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * Generates a List when + * findCommonFiles() is called, which organizes files by md5 to + * prepare to display in viewer. + * + * This entire thing runs on a background thread where exceptions are handled. + */ +@SuppressWarnings("PMD.AbstractNaming") +abstract class CommonFilesMetadataBuilder { + + private final Map dataSourceIdToNameMap; + private final boolean filterByMedia; + private final boolean filterByDoc; + private static final String filterByMimeTypesWhereClause = " and mime_type in (%s)"; //NON-NLS // where %s is csv list of mime_types to filter on + + /* + * The set of the MIME types that will be checked for extension mismatches + * when checkType is ONLY_MEDIA. + * ".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec" + * ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS + * ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf" + */ + private static final Set MEDIA_PICS_VIDEO_MIME_TYPES = Stream.of( + "image/bmp", //NON-NLS + "image/gif", //NON-NLS + "image/jpeg", //NON-NLS + "image/png", //NON-NLS + "image/tiff", //NON-NLS + "image/vnd.adobe.photoshop", //NON-NLS + "image/x-raw-nikon", //NON-NLS + "image/x-ms-bmp", //NON-NLS + "image/x-icon", //NON-NLS + "video/webm", //NON-NLS + "video/3gpp", //NON-NLS + "video/3gpp2", //NON-NLS + "video/ogg", //NON-NLS + "video/mpeg", //NON-NLS + "video/mp4", //NON-NLS + "video/quicktime", //NON-NLS + "video/x-msvideo", //NON-NLS + "video/x-flv", //NON-NLS + "video/x-m4v", //NON-NLS + "video/x-ms-wmv", //NON-NLS + "application/vnd.ms-asf", //NON-NLS + "application/vnd.rn-realmedia", //NON-NLS + "application/x-shockwave-flash" //NON-NLS + ).collect(Collectors.toSet()); + + /* + * The set of the MIME types that will be checked for extension mismatches + * when checkType is ONLY_TEXT_FILES. + * ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx" + * ".txt", ".rtf", ".log", ".text", ".xml" + * ".html", ".htm", ".css", ".js", ".php", ".aspx" + * ".pdf" + */ + private static final Set TEXT_FILES_MIME_TYPES = Stream.of( + "text/plain", //NON-NLS + "application/rtf", //NON-NLS + "application/pdf", //NON-NLS + "text/css", //NON-NLS + "text/html", //NON-NLS + "text/csv", //NON-NLS + "application/json", //NON-NLS + "application/javascript", //NON-NLS + "application/xml", //NON-NLS + "text/calendar", //NON-NLS + "application/x-msoffice", //NON-NLS + "application/x-ooxml", //NON-NLS + "application/msword", //NON-NLS + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS + "application/vnd.ms-powerpoint", //NON-NLS + "application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS + "application/vnd.ms-excel", //NON-NLS + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS + "application/vnd.oasis.opendocument.presentation", //NON-NLS + "application/vnd.oasis.opendocument.spreadsheet", //NON-NLS + "application/vnd.oasis.opendocument.text" //NON-NLS + ).collect(Collectors.toSet()); + + /** + * Subclass this to implement different algorithms for getting common files. + * + * @param dataSourceIdMap a map of obj_id to datasource name + * @param filterByMediaMimeType match only on files whose mime types can be + * broadly categorized as media types + * @param filterByDocMimeType match only on files whose mime types can be + * broadly categorized as document types + */ + CommonFilesMetadataBuilder(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + dataSourceIdToNameMap = dataSourceIdMap; + filterByMedia = filterByMediaMimeType; + filterByDoc = filterByDocMimeType; + } + + /** + * Use this as a prefix when building the SQL select statement. + * + *
    + *
  • You only have to specify the WHERE clause if you use this.
  • + *
  • If you do not use this string, you must use at least the columns + * selected below, in that order.
  • + *
+ */ + static final String SELECT_PREFIX = "SELECT obj_id, md5, data_source_obj_id from tsk_files where"; //NON-NLS + + /** + * Should build a SQL SELECT statement to be passed to + * SleuthkitCase.executeQuery(sql) which will select the desired file ids + * and MD5 hashes. + * + * The statement should select obj_id, md5, data_source_obj_id in that + * order. + * + * @return sql string select statement + */ + protected abstract String buildSqlSelectStatement(); + + /** + * Generate a meta data object which encapsulates everything need to add the + * tree table tab to the top component. + * + * @return a data object with all of the matched files in a hierarchical + * format + * @throws TskCoreException + * @throws NoCurrentCaseException + * @throws SQLException + */ + public CommonFilesMetadata findCommonFiles() throws TskCoreException, NoCurrentCaseException, SQLException { + + Map commonFiles = new HashMap<>(); + + SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + String selectStatement = this.buildSqlSelectStatement(); + + try ( + CaseDbQuery query = sleuthkitCase.executeQuery(selectStatement); + ResultSet resultSet = query.getResultSet()) { + + while (resultSet.next()) { + Long objectId = resultSet.getLong(1); + String md5 = resultSet.getString(2); + Long dataSourceId = resultSet.getLong(3); + String dataSource = this.dataSourceIdToNameMap.get(dataSourceId); + + if (md5 == null || HashUtility.isNoDataMd5(md5)) { + continue; + } + + if (commonFiles.containsKey(md5)) { + final Md5Metadata md5Metadata = commonFiles.get(md5); + md5Metadata.addFileInstanceMetadata(new FileInstanceMetadata(objectId, dataSource)); + } else { + final List fileInstances = new ArrayList<>(); + fileInstances.add(new FileInstanceMetadata(objectId, dataSource)); + Md5Metadata md5Metadata = new Md5Metadata(md5, fileInstances); + commonFiles.put(md5, md5Metadata); + } + } + } + + return new CommonFilesMetadata(commonFiles); + } + + /** + * Should be used by subclasses, in their + * buildSqlSelectStatement() function to create an SQL boolean + * expression which will filter our matches based on mime type. The + * expression will be conjoined to base query with an AND operator. + * + * @return sql fragment of the form: + * 'and "mime_type" in ( [comma delimited list of mime types] )' + * or empty string in the event that no types to filter on were given. + */ + String determineMimeTypeFilter() { + + Set mimeTypesToFilterOn = new HashSet<>(); + String mimeTypeString = ""; + if (filterByMedia) { + mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES); + } + if (filterByDoc) { + mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES); + } + StringBuilder mimeTypeFilter = new StringBuilder(mimeTypesToFilterOn.size()); + if (!mimeTypesToFilterOn.isEmpty()) { + for (String mimeType : mimeTypesToFilterOn) { + mimeTypeFilter.append('"').append(mimeType).append("\","); + } + mimeTypeString = mimeTypeFilter.toString().substring(0, mimeTypeFilter.length() - 1); + mimeTypeString = String.format(filterByMimeTypesWhereClause, new Object[]{mimeTypeString}); + } + return mimeTypeString; + } + + @NbBundle.Messages({ + "CommonFilesMetadataBuilder.buildTabTitle.titleAll=Common Files (All Data Sources, %s)", + "CommonFilesMetadataBuilder.buildTabTitle.titleSingle=Common Files (Match Within Data Source: %s, %s)" + }) + protected abstract String buildTabTitle(); + + @NbBundle.Messages({ + "CommonFilesMetadataBuilder.buildCategorySelectionString.doc=Documents", + "CommonFilesMetadataBuilder.buildCategorySelectionString.media=Media", + "CommonFilesMetadataBuilder.buildCategorySelectionString.all=All File Categories" + }) + protected String buildCategorySelectionString() { + if (!this.filterByDoc && !this.filterByMedia) { + return Bundle.CommonFilesMetadataBuilder_buildCategorySelectionString_all(); + } else { + List filters = new ArrayList<>(); + if (this.filterByDoc) { + filters.add(Bundle.CommonFilesMetadataBuilder_buildCategorySelectionString_doc()); + } + if (this.filterByMedia) { + filters.add(Bundle.CommonFilesMetadataBuilder_buildCategorySelectionString_media()); + } + return String.join(", ", filters); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java new file mode 100644 index 0000000000..0226baff58 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java @@ -0,0 +1,95 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.util.List; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.datamodel.Md5Node; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; + +/** + * Wrapper node for Md5Node used to display common files search + * results in the top right pane. Calls Md5NodeFactory. + */ +final public class CommonFilesNode extends DisplayableItemNode { + + CommonFilesNode(CommonFilesMetadata metadataList) { + super(Children.create(new Md5NodeFactory(metadataList), true), Lookups.singleton(CommonFilesNode.class)); + } + + @NbBundle.Messages({ + "CommonFilesNode.getName.text=Common Files"}) + @Override + public String getName() { + return Bundle.CommonFilesNode_getName_text(); + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /** + * ChildFactory which builds CommonFileParentNodes from the + * CommonFilesMetaaData models. + */ + static class Md5NodeFactory extends ChildFactory { + + /** + * List of models, each of which is a parent node matching a single md5, + * containing children FileNodes. + */ + private CommonFilesMetadata metadata; + + Md5NodeFactory(CommonFilesMetadata metadata) { + this.metadata = metadata; + } + + protected void removeNotify() { + metadata = null; + } + + @Override + protected Node createNodeForKey(String md5) { + Md5Metadata metadata = this.metadata.getMetadataForMd5(md5); + return new Md5Node(metadata); + } + + @Override + protected boolean createKeys(List list) { + list.addAll(this.metadata.getMetadata().keySet()); + return true; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.form new file mode 100644 index 0000000000..c9d08c0536 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.form @@ -0,0 +1,261 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java new file mode 100644 index 0000000000..ad278a4cb5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -0,0 +1,616 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.io.File; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.ComboBoxModel; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import org.openide.explorer.ExplorerManager; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Panel used for common files search configuration and configuration business + * logic. Nested within CommonFilesDialog. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public final class CommonFilesPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private static final Long NO_DATA_SOURCE_SELECTED = -1L; + + private ComboBoxModel dataSourcesList = new DataSourceComboBoxModel(); + private Map dataSourceMap; + + private static final Logger LOGGER = Logger.getLogger(CommonFilesPanel.class.getName()); + private boolean singleDataSource = false; + private String selectedDataSource = ""; + private boolean pictureViewCheckboxState; + private boolean documentsCheckboxState; + + /** + * Creates new form CommonFilesPanel + */ + @NbBundle.Messages({ + "CommonFilesPanel.title=Common Files Panel", + "CommonFilesPanel.exception=Unexpected Exception loading DataSources."}) + public CommonFilesPanel() { + initComponents(); + + this.setupDataSources(); + + this.errorText.setVisible(false); + } + + /** + * Sets up the data sources dropdown and returns the data sources map for + * future usage. + * + * @return a mapping of data source ids to data source names + */ + @NbBundle.Messages({ + "CommonFilesPanel.buildDataSourceMap.done.tskCoreException=Unable to run query against DB.", + "CommonFilesPanel.buildDataSourceMap.done.noCurrentCaseException=Unable to open case file.", + "CommonFilesPanel.buildDataSourceMap.done.exception=Unexpected exception building data sources map.", + "CommonFilesPanel.buildDataSourceMap.done.interupted=Something went wrong building the Common Files Search dialog box.", + "CommonFilesPanel.buildDataSourceMap.done.sqlException=Unable to query db for data sources.", + "CommonFilesPanel.buildDataSourcesMap.updateUi.noDataSources=No data sources were found."}) + private void setupDataSources() { + + new SwingWorker, Void>() { + + private static final String SELECT_DATA_SOURCES_LOGICAL = "select obj_id, name from tsk_files where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))"; + + private static final String SELECT_DATA_SOURCES_IMAGE = "select obj_id, name from tsk_image_names where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))"; + + private void updateUi() { + + String[] dataSourcesNames = new String[CommonFilesPanel.this.dataSourceMap.size()]; + + //only enable all this stuff if we actually have datasources + if (dataSourcesNames.length > 0) { + dataSourcesNames = CommonFilesPanel.this.dataSourceMap.values().toArray(dataSourcesNames); + CommonFilesPanel.this.dataSourcesList = new DataSourceComboBoxModel(dataSourcesNames); + CommonFilesPanel.this.selectDataSourceComboBox.setModel(CommonFilesPanel.this.dataSourcesList); + + boolean multipleDataSources = this.caseHasMultipleSources(); + CommonFilesPanel.this.allDataSourcesRadioButton.setEnabled(multipleDataSources); + CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(multipleDataSources); + + if (!multipleDataSources) { + CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(true); + withinDataSourceSelected(true); + } + + CommonFilesPanel.this.searchButton.setEnabled(true); + } else { + MessageNotifyUtil.Message.info(Bundle.CommonFilesPanel_buildDataSourcesMap_updateUi_noDataSources()); + CommonFilesPanel.this.cancelButtonActionPerformed(null); + } + } + + private boolean caseHasMultipleSources() { + return CommonFilesPanel.this.dataSourceMap.size() >= 2; + } + + private void loadLogicalSources(SleuthkitCase tskDb, Map dataSouceMap) throws TskCoreException, SQLException { + //try block releases resources - exceptions are handled in done() + try ( + CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_LOGICAL); + ResultSet resultSet = query.getResultSet()) { + while (resultSet.next()) { + Long objectId = resultSet.getLong(1); + String dataSourceName = resultSet.getString(2); + dataSouceMap.put(objectId, dataSourceName); + } + } + } + + private void loadImageSources(SleuthkitCase tskDb, Map dataSouceMap) throws SQLException, TskCoreException { + //try block releases resources - exceptions are handled in done() + try ( + CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_IMAGE); + ResultSet resultSet = query.getResultSet()) { + + while (resultSet.next()) { + Long objectId = resultSet.getLong(1); + String dataSourceName = resultSet.getString(2); + File image = new File(dataSourceName); + String dataSourceNameTrimmed = image.getName(); + dataSouceMap.put(objectId, dataSourceNameTrimmed); + } + } + } + + @Override + protected Map doInBackground() throws NoCurrentCaseException, TskCoreException, SQLException { + + Map dataSouceMap = new HashMap<>(); + + Case currentCase = Case.getCurrentCaseThrows(); + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + + loadLogicalSources(tskDb, dataSouceMap); + + loadImageSources(tskDb, dataSouceMap); + + return dataSouceMap; + } + + @Override + protected void done() { + + try { + CommonFilesPanel.this.dataSourceMap = this.get(); + + updateUi(); + + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "Interrupted while building Common Files Search dialog.", ex); + MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_buildDataSourceMap_done_interupted()); + } catch (ExecutionException ex) { + String errorMessage; + Throwable inner = ex.getCause(); + if (inner instanceof TskCoreException) { + LOGGER.log(Level.SEVERE, "Failed to load data sources from database.", ex); + errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_tskCoreException(); + } else if (inner instanceof NoCurrentCaseException) { + LOGGER.log(Level.SEVERE, "Current case has been closed.", ex); + errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_noCurrentCaseException(); + } else if (inner instanceof SQLException) { + LOGGER.log(Level.SEVERE, "Unable to query db for data sources.", ex); + errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_sqlException(); + } else { + LOGGER.log(Level.SEVERE, "Unexpected exception while building Common Files Search dialog panel.", ex); + errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_exception(); + } + MessageNotifyUtil.Message.error(errorMessage); + } + } + }.execute(); + } + + @NbBundle.Messages({ + "CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)", + "CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)", + "CommonFilesPanel.search.results.pathText=Common Files Search Results", + "CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.", + "CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.", + "CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.", + "CommonFilesPanel.search.done.interupted=Something went wrong finding common files.", + "CommonFilesPanel.search.done.sqlException=Unable to query db for files or data sources."}) + private void search() { + String pathText = Bundle.CommonFilesPanel_search_results_pathText(); + + new SwingWorker() { + + private String tabTitle; + + private void setTitleForAllDataSources() { + this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll(); + } + + private void setTitleForSingleSource(Long dataSourceId) { + final String CommonFilesPanel_search_results_titleSingle = Bundle.CommonFilesPanel_search_results_titleSingle(); + final Object[] dataSourceName = new Object[]{dataSourceMap.get(dataSourceId)}; + + this.tabTitle = String.format(CommonFilesPanel_search_results_titleSingle, dataSourceName); + } + + private Long determineDataSourceId() { + Long selectedObjId = CommonFilesPanel.NO_DATA_SOURCE_SELECTED; + if (CommonFilesPanel.this.singleDataSource) { + for (Entry dataSource : CommonFilesPanel.this.dataSourceMap.entrySet()) { + if (dataSource.getValue().equals(CommonFilesPanel.this.selectedDataSource)) { + selectedObjId = dataSource.getKey(); + break; + } + } + } + return selectedObjId; + } + + @Override + @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) + protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException { + Long dataSourceId = determineDataSourceId(); + + CommonFilesMetadataBuilder builder; + boolean filterByMedia = false; + boolean filterByDocuments = false; + if (selectedFileCategoriesButton.isSelected()) { + if (pictureVideoCheckbox.isSelected()) { + filterByMedia = true; + } + if (documentsCheckbox.isSelected()) { + filterByDocuments = true; + } + } + if (dataSourceId == CommonFilesPanel.NO_DATA_SOURCE_SELECTED) { + builder = new AllDataSourcesCommonFilesAlgorithm(CommonFilesPanel.this.dataSourceMap, filterByMedia, filterByDocuments); + + setTitleForAllDataSources(); + } else { + builder = new SingleDataSource(dataSourceId, CommonFilesPanel.this.dataSourceMap, filterByMedia, filterByDocuments); + + setTitleForSingleSource(dataSourceId); + } + + this.tabTitle = builder.buildTabTitle(); + + CommonFilesMetadata metadata = builder.findCommonFiles(); + + return metadata; + } + + @Override + protected void done() { + try { + super.done(); + + CommonFilesMetadata metadata = get(); + + CommonFilesNode commonFilesNode = new CommonFilesNode(metadata); + + DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonFilesPanel.this)); + + TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode); + + DataResultViewerTable table = new DataResultViewerTable(); + + Collection viewers = new ArrayList<>(1); + viewers.add(table); + + DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers); + + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex); + MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted()); + } catch (ExecutionException ex) { + String errorMessage; + Throwable inner = ex.getCause(); + if (inner instanceof TskCoreException) { + LOGGER.log(Level.SEVERE, "Failed to load files from database.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_tskCoreException(); + } else if (inner instanceof NoCurrentCaseException) { + LOGGER.log(Level.SEVERE, "Current case has been closed.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_noCurrentCaseException(); + } else if (inner instanceof SQLException) { + LOGGER.log(Level.SEVERE, "Unable to query db for files.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_sqlException(); + } else { + LOGGER.log(Level.SEVERE, "Unexpected exception while running Common Files Search.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_exception(); + } + MessageNotifyUtil.Message.error(errorMessage); + } + } + }.execute(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + dataSourcesButtonGroup = new javax.swing.ButtonGroup(); + fileTypeFilterButtonGroup = new javax.swing.ButtonGroup(); + searchButton = new javax.swing.JButton(); + allDataSourcesRadioButton = new javax.swing.JRadioButton(); + withinDataSourceRadioButton = new javax.swing.JRadioButton(); + selectDataSourceComboBox = new javax.swing.JComboBox<>(); + commonFilesSearchLabel = new javax.swing.JLabel(); + cancelButton = new javax.swing.JButton(); + allFileCategoriesRadioButton = new javax.swing.JRadioButton(); + selectedFileCategoriesButton = new javax.swing.JRadioButton(); + pictureVideoCheckbox = new javax.swing.JCheckBox(); + documentsCheckbox = new javax.swing.JCheckBox(); + dataSourceLabel = new javax.swing.JLabel(); + categoriesLabel = new javax.swing.JLabel(); + errorText = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.searchButton.text")); // NOI18N + searchButton.setEnabled(false); + searchButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + searchButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchButtonActionPerformed(evt); + } + }); + + dataSourcesButtonGroup.add(allDataSourcesRadioButton); + allDataSourcesRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(allDataSourcesRadioButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.allDataSourcesRadioButton.text")); // NOI18N + allDataSourcesRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + allDataSourcesRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allDataSourcesRadioButtonActionPerformed(evt); + } + }); + + dataSourcesButtonGroup.add(withinDataSourceRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(withinDataSourceRadioButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.withinDataSourceRadioButton.text")); // NOI18N + withinDataSourceRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + withinDataSourceRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + withinDataSourceRadioButtonActionPerformed(evt); + } + }); + + selectDataSourceComboBox.setModel(dataSourcesList); + selectDataSourceComboBox.setEnabled(false); + selectDataSourceComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + selectDataSourceComboBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.commonFilesSearchLabel.text")); // NOI18N + commonFilesSearchLabel.setFocusable(false); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.cancelButton.text")); // NOI18N + cancelButton.setActionCommand(org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.cancelButton.actionCommand")); // NOI18N + cancelButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + fileTypeFilterButtonGroup.add(allFileCategoriesRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.allFileCategoriesRadioButton.text")); // NOI18N + allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N + allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allFileCategoriesRadioButtonActionPerformed(evt); + } + }); + + fileTypeFilterButtonGroup.add(selectedFileCategoriesButton); + selectedFileCategoriesButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.selectedFileCategoriesButton.text")); // NOI18N + selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.selectedFileCategoriesButton.toolTipText")); // NOI18N + selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + selectedFileCategoriesButtonActionPerformed(evt); + } + }); + + pictureVideoCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.pictureVideoCheckbox.text")); // NOI18N + pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pictureVideoCheckboxActionPerformed(evt); + } + }); + + documentsCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.documentsCheckbox.text")); // NOI18N + documentsCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + documentsCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(dataSourceLabel, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.text")); // NOI18N + dataSourceLabel.setName(""); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(categoriesLabel, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.categoriesLabel.text")); // NOI18N + categoriesLabel.setName(""); // NOI18N + + errorText.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(errorText, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.errorText.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(errorText) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(searchButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(categoriesLabel) + .addComponent(commonFilesSearchLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(dataSourceLabel) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(29, 29, 29) + .addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(withinDataSourceRadioButton) + .addComponent(allDataSourcesRadioButton) + .addComponent(allFileCategoriesRadioButton) + .addComponent(selectedFileCategoriesButton) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pictureVideoCheckbox) + .addComponent(documentsCheckbox)))))) + .addGap(19, 19, 19)))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(commonFilesSearchLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(dataSourceLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(allDataSourcesRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(withinDataSourceRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(categoriesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(selectedFileCategoriesButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pictureVideoCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(documentsCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(allFileCategoriesRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton) + .addComponent(searchButton) + .addComponent(errorText))) + ); + }// //GEN-END:initComponents + + private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed + search(); + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_searchButtonActionPerformed + + private void allDataSourcesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allDataSourcesRadioButtonActionPerformed + selectDataSourceComboBox.setEnabled(!allDataSourcesRadioButton.isSelected()); + singleDataSource = false; + }//GEN-LAST:event_allDataSourcesRadioButtonActionPerformed + + private void selectDataSourceComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectDataSourceComboBoxActionPerformed + final Object selectedItem = selectDataSourceComboBox.getSelectedItem(); + if (selectedItem != null) { + selectedDataSource = selectedItem.toString(); + } else { + selectedDataSource = ""; + } + }//GEN-LAST:event_selectDataSourceComboBoxActionPerformed + + private void withinDataSourceRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_withinDataSourceRadioButtonActionPerformed + withinDataSourceSelected(withinDataSourceRadioButton.isSelected()); + }//GEN-LAST:event_withinDataSourceRadioButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed + this.manageCheckBoxState(); + this.toggleErrorTextAndSearchBox(); + }//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed + + private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed + this.manageCheckBoxState(); + }//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed + + private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed + this.toggleErrorTextAndSearchBox(); + }//GEN-LAST:event_pictureVideoCheckboxActionPerformed + + private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed + this.toggleErrorTextAndSearchBox(); + }//GEN-LAST:event_documentsCheckboxActionPerformed + + private void toggleErrorTextAndSearchBox() { + if (!this.pictureVideoCheckbox.isSelected() && !this.documentsCheckbox.isSelected() && !this.allFileCategoriesRadioButton.isSelected()) { + this.searchButton.setEnabled(false); + this.errorText.setVisible(true); + } else { + this.searchButton.setEnabled(true); + this.errorText.setVisible(false); + } + } + + private void withinDataSourceSelected(boolean selected) { + selectDataSourceComboBox.setEnabled(selected); + if (selectDataSourceComboBox.isEnabled()) { + selectDataSourceComboBox.setSelectedIndex(0); + singleDataSource = true; + } + } + + private void manageCheckBoxState() { + + if (this.allFileCategoriesRadioButton.isSelected()) { + + this.pictureViewCheckboxState = this.pictureVideoCheckbox.isSelected(); + this.documentsCheckboxState = this.documentsCheckbox.isSelected(); + + this.pictureVideoCheckbox.setEnabled(false); + this.documentsCheckbox.setEnabled(false); + } + + if (this.selectedFileCategoriesButton.isSelected()) { + + this.pictureVideoCheckbox.setSelected(this.pictureViewCheckboxState); + this.documentsCheckbox.setSelected(this.documentsCheckboxState); + + this.pictureVideoCheckbox.setEnabled(true); + this.documentsCheckbox.setEnabled(true); + + this.toggleErrorTextAndSearchBox(); + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allDataSourcesRadioButton; + private javax.swing.JRadioButton allFileCategoriesRadioButton; + private javax.swing.JButton cancelButton; + private javax.swing.JLabel categoriesLabel; + private javax.swing.JLabel commonFilesSearchLabel; + private javax.swing.JLabel dataSourceLabel; + private javax.swing.ButtonGroup dataSourcesButtonGroup; + private javax.swing.JCheckBox documentsCheckbox; + private javax.swing.JLabel errorText; + private javax.swing.ButtonGroup fileTypeFilterButtonGroup; + private javax.swing.JCheckBox pictureVideoCheckbox; + private javax.swing.JButton searchButton; + private javax.swing.JComboBox selectDataSourceComboBox; + private javax.swing.JRadioButton selectedFileCategoriesButton; + private javax.swing.JRadioButton withinDataSourceRadioButton; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java new file mode 100644 index 0000000000..e470fd16bc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.awt.event.ActionEvent; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.core.Installer; + +/** + * Encapsulates a menu action which triggers the common files search dialog. + */ +final public class CommonFilesSearchAction extends CallableSystemAction { + + private static CommonFilesSearchAction instance = null; + private static final long serialVersionUID = 1L; + + CommonFilesSearchAction() { + super(); + this.setEnabled(false); + } + + @Override + public boolean isEnabled(){ + return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); + } + + public static synchronized CommonFilesSearchAction getDefault() { + if (instance == null) { + instance = new CommonFilesSearchAction(); + } + return instance; + } + + @Override + public void actionPerformed(ActionEvent event) { + new CommonFilesDialog().setVisible(true); + } + + @Override + public void performAction() { + new CommonFilesDialog().setVisible(true); + } + + @NbBundle.Messages({ + "CommonFilesAction.getName.text=Common Files Search"}) + @Override + public String getName() { + return Bundle.CommonFilesAction_getName_text(); + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java new file mode 100644 index 0000000000..657fedc53b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java @@ -0,0 +1,80 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.event.ListDataListener; + +/** + * Encapsulates meta data needed to populate the data source selection drop down menu + */ +public class DataSourceComboBoxModel extends AbstractListModel implements ComboBoxModel { + + private static final long serialVersionUID = 1L; + private final String[] dataSourceList; + String selection = null; + + /** + * Use this to initialize the panel + */ + DataSourceComboBoxModel() { + this.dataSourceList = new String[0]; + } + + /** + * Use this when we have data to display. + * + * @param theDataSoureList names of data sources for user to pick from + */ + DataSourceComboBoxModel(String... theDataSoureList) { + dataSourceList = theDataSoureList.clone(); + } + + @Override + public void setSelectedItem(Object anItem) { + selection = (String) anItem; + } + + @Override + public Object getSelectedItem() { + return selection; + } + + @Override + public int getSize() { + return dataSourceList.length; + } + + @Override + public String getElementAt(int index) { + return dataSourceList[index]; + } + + @Override + public void addListDataListener(ListDataListener listener) { + this.listenerList.add(ListDataListener.class, listener); + } + + @Override + public void removeListDataListener(ListDataListener listener) { + this.listenerList.remove(ListDataListener.class, listener); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceMetadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceMetadata.java new file mode 100644 index 0000000000..6284316013 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceMetadata.java @@ -0,0 +1,59 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +/** + * Encapsulates data required to instantiate a FileInstanceNode. + */ +final public class FileInstanceMetadata { + + private final Long objectId; + private final String dataSourceName; + + /** + * Create meta data required to find an abstract file and build a + * FileInstanceNode. + * + * @param objectId id of abstract file to find + * @param dataSourceName name of datasource where the object is found + */ + FileInstanceMetadata(Long objectId, String dataSourceName) { + this.objectId = objectId; + this.dataSourceName = dataSourceName; + } + + /** + * obj_id for the file represented by this object + * + * @return + */ + public Long getObjectId() { + return this.objectId; + } + + /** + * Name of datasource where this instance was found. + * + * @return + */ + public String getDataSourceName() { + return this.dataSourceName; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java new file mode 100644 index 0000000000..39c2cf8040 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java @@ -0,0 +1,68 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Encapsulates data required to instantiate an Md5Node. + */ +final public class Md5Metadata { + + private final String md5; + private final List fileInstances; + + Md5Metadata(String md5, List fileInstances){ + this.md5 = md5; + this.fileInstances = fileInstances; + } + + public String getMd5(){ + return this.md5; + } + + void addFileInstanceMetadata(FileInstanceMetadata metadata){ + this.fileInstances.add(metadata); + } + + public Collection getMetadata(){ + return Collections.unmodifiableCollection(this.fileInstances); + } + + /** + * How many distinct file instances exist for the MD5 represented by this object? + * @return number of instances + */ + public int size(){ + return this.fileInstances.size(); + } + + public String getDataSources() { + Set sources = new HashSet<> (); + for(FileInstanceMetadata data : this.fileInstances){ + sources.add(data.getDataSourceName()); + } + return String.join(", ", sources); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java new file mode 100644 index 0000000000..a3643d3306 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java @@ -0,0 +1,61 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilesearch; + +import java.util.Map; + +/** + * Provides logic for selecting common files from a single data source. + */ +final class SingleDataSource extends CommonFilesMetadataBuilder { + + private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where md5 in (select md5 from tsk_files where (known != 1 OR known IS NULL) and data_source_obj_id=%s%s) GROUP BY md5 HAVING COUNT(*) > 1) order by md5"; //NON-NLS + private final Long selectedDataSourceId; + private final String dataSourceName; + + /** + * Implements the algorithm for getting common files that appear at least + * once in the given data source. + * + * @param dataSourceId data source id for which common files must appear at + * least once + * @param dataSourceIdMap a map of obj_id to datasource name + * @param filterByMediaMimeType match only on files whose mime types can be broadly categorized as media types + * @param filterByDocMimeType match only on files whose mime types can be broadly categorized as document types + */ + SingleDataSource(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + this.selectedDataSourceId = dataSourceId; + this.dataSourceName = dataSourceIdMap.get(this.selectedDataSourceId); + } + + @Override + protected String buildSqlSelectStatement() { + Object[] args = new String[]{SELECT_PREFIX, Long.toString(this.selectedDataSourceId), determineMimeTypeFilter()}; + return String.format(SingleDataSource.WHERE_CLAUSE, args); + } + + @Override + protected String buildTabTitle() { + final String buildCategorySelectionString = this.buildCategorySelectionString(); + final String titleTemplate = Bundle.CommonFilesMetadataBuilder_buildTabTitle_titleSingle(); + return String.format(titleTemplate, new Object[]{this.dataSourceName, buildCategorySelectionString}); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java index 244838d8ef..2262e6af61 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java @@ -104,7 +104,7 @@ final class AccountDeviceInstanceKey { private static String getDataSourceName(AccountDeviceInstance accountDeviceInstance) { try { - SleuthkitCase db = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase db = Case.getCurrentCaseThrows().getSleuthkitCase(); for (DataSource dataSource : db.getDataSources()) { if (dataSource.getDeviceId().equals(accountDeviceInstance.getDeviceId())) { return db.getContentById(dataSource.getId()).getName(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index 044cec322f..0a57c3728f 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -74,11 +74,11 @@ final class AccountDeviceInstanceNode extends AbstractNode { @Override @NbBundle.Messages(value = {"AccountNode.device=Device", "AccountNode.accountName=Account", "AccountNode.accountType=Type", "AccountNode.messageCount=Msgs"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set properties = s.get(Sheet.PROPERTIES); + Sheet sheet = super.createSheet(); + Sheet.Set properties = sheet.get(Sheet.PROPERTIES); if (properties == null) { properties = Sheet.createPropertiesSet(); - s.put(properties); + sheet.put(properties); } properties.put(new NodeProperty<>("type", Bundle.AccountNode_accountType(), @@ -92,7 +92,7 @@ final class AccountDeviceInstanceNode extends AbstractNode { Bundle.AccountNode_device(), "device", accountDeviceInstanceKey.getDataSourceName())); // NON-NLS - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java index 534da9d557..a0417855fe 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -121,7 +121,7 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro @Subscribe public void handleFilterEvent(CVTEvents.FilterChangeEvent filterChangeEvent) { try { - final CommunicationsManager commsManager = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager(); + final CommunicationsManager commsManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); accountsTableEM.setRootContext(new AbstractNode(Children.create(new AccountDeviceInstanceNodeFactory(commsManager, filterChangeEvent.getNewFilter()), true))); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "There was an error getting the CommunicationsManager for the current case.", ex); diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index c3d69ed28d..6cd46fbab4 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -209,7 +209,7 @@ final public class FiltersPanel extends JPanel { //TODO: something like this commented code could be used to show only //the account types that are found: - //final CommunicationsManager communicationsManager = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager(); + //final CommunicationsManager communicationsManager = Case.getCurrentOpenCase().getSleuthkitCase().getCommunicationsManager(); //List accountTypesInUse = communicationsManager.getAccountTypesInUse(); //accountTypesInUSe.forEach(...) Account.Type.PREDEFINED_ACCOUNT_TYPES.forEach(type -> { @@ -240,7 +240,7 @@ final public class FiltersPanel extends JPanel { */ private void updateDeviceFilter(boolean initialState) { try { - final SleuthkitCase sleuthkitCase = Case.getOpenCase().getSleuthkitCase(); + final SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); for (DataSource dataSource : sleuthkitCase.getDataSources()) { String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 7b90357993..dca1770463 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -128,7 +128,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov public void propertyChange(PropertyChangeEvent pce) { if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes(); - messagesResultPanel.setNumMatches(0); + messagesResultPanel.setNumberOfChildNodes(0); messagesResultPanel.setNode(null); messagesResultPanel.setPath(""); if (selectedNodes.length > 0) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java index d83eaa4fa3..eb448f4f8a 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java @@ -56,14 +56,14 @@ final class RelationshipNode extends BlackboardArtifactNode { @Override protected Sheet createSheet() { - Sheet s = new Sheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName())); + sheetSet.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName())); final BlackboardArtifact artifact = getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID()); @@ -71,42 +71,42 @@ final class RelationshipNode extends BlackboardArtifactNode { //Consider refactoring this to reduce boilerplate switch (fromID) { case TSK_EMAIL_MSG: - ss.put(new NodeProperty<>("From", "From", "From", + sheetSet.put(new NodeProperty<>("From", "From", "From", StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); - ss.put(new NodeProperty<>("To", "To", "To", + sheetSet.put(new NodeProperty<>("To", "To", "To", StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); - ss.put(new NodeProperty<>("Date", "Date", "Date", + sheetSet.put(new NodeProperty<>("Date", "Date", "Date", getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); - ss.put(new NodeProperty<>("Subject", "Subject", "Subject", + sheetSet.put(new NodeProperty<>("Subject", "Subject", "Subject", getAttributeDisplayString(artifact, TSK_SUBJECT))); try { - ss.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); + sheetSet.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); } break; case TSK_MESSAGE: - ss.put(new NodeProperty<>("From", "From", "From", + sheetSet.put(new NodeProperty<>("From", "From", "From", getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); - ss.put(new NodeProperty<>("To", "To", "To", + sheetSet.put(new NodeProperty<>("To", "To", "To", getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); - ss.put(new NodeProperty<>("Date", "Date", "Date", + sheetSet.put(new NodeProperty<>("Date", "Date", "Date", getAttributeDisplayString(artifact, TSK_DATETIME))); - ss.put(new NodeProperty<>("Subject", "Subject", "Subject", + sheetSet.put(new NodeProperty<>("Subject", "Subject", "Subject", getAttributeDisplayString(artifact, TSK_SUBJECT))); try { - ss.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); + sheetSet.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); } break; case TSK_CALLLOG: - ss.put(new NodeProperty<>("From", "From", "From", + sheetSet.put(new NodeProperty<>("From", "From", "From", getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); - ss.put(new NodeProperty<>("To", "To", "To", + sheetSet.put(new NodeProperty<>("To", "To", "To", getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); - ss.put(new NodeProperty<>("Date", "Date", "Date", + sheetSet.put(new NodeProperty<>("Date", "Date", "Date", getAttributeDisplayString(artifact, TSK_DATETIME_START))); break; default: @@ -114,9 +114,9 @@ final class RelationshipNode extends BlackboardArtifactNode { } } - addTagProperty(ss); + addTagProperty(sheetSet); - return s; + return sheet; } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 4f6b53ae39..8d0743b272 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -208,11 +208,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider applyLayout(fastOrganicLayout); } - /** - * - * @param layoutButton the value of layoutButton - * @param layout the value of layout - */ @Override public Lookup getLookup() { return proxyLookup; @@ -300,7 +295,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider windowAncestor = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this); try { - commsManager = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager(); + commsManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); } catch (NoCurrentCaseException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java index 723c1ef0f3..7f618c0754 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Custom Cell renderer to display a SQLite column cell as readable Epoch date/time * */ -public class EpochTimeCellRenderer extends DefaultTableCellRenderer { +class EpochTimeCellRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java index ef6acc1fd4..cc5e2008b5 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java @@ -139,7 +139,7 @@ public class FXVideoPanel extends MediaViewVideoPanel { final File tempFile; try { - tempFile = VideoUtils.getTempVideoFile(currentFile); + tempFile = VideoUtils.getVideoFileInTempDir(currentFile); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/FileTypeViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java similarity index 77% rename from Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/FileTypeViewer.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java index b3ae7210a2..a2096dd2d9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/FileTypeViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.corecomponentinterfaces; +package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.util.List; @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.AbstractFile; * Defines an interface for application specific content viewer * */ -public interface FileTypeViewer { +interface FileTypeViewer { /** * Returns list of MIME types supported by this viewer @@ -45,6 +45,11 @@ public interface FileTypeViewer { /** * Clears the data in the panel + * + * IMPORTANT IF MAKING THIS PUBLIC: I (RC) am not sure that this method + * belongs in this interface. If we are not going to use setFile(null) as a + * reset method as in DataContentViewer and DataResultViewer, then this is + * fine. Otherwise, it is ambiguous. */ void resetComponent(); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index 98cee5bc59..53be8ddbcd 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -31,7 +31,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.autopsy.corecomponentinterfaces.FileTypeViewer; /** * Generic Application content viewer diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java index c686471a41..5ebd894517 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java @@ -198,7 +198,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { java.io.File ioFile; try { - ioFile = VideoUtils.getTempVideoFile(file); + ioFile = VideoUtils.getVideoFileInTempDir(file); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS infoLabel.setText(Bundle.GstVideoPanel_noOpenCase_errMsg()); @@ -552,7 +552,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { } else if (state.equals(State.READY)) { final File tempVideoFile; try { - tempVideoFile = VideoUtils.getTempVideoFile(currentFile); + tempVideoFile = VideoUtils.getVideoFileInTempDir(currentFile); } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Exception while getting open case."); //NON-NLS infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java index 1d9695712b..92da0733d8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java @@ -24,14 +24,13 @@ import java.awt.Dimension; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; -import org.sleuthkit.autopsy.corecomponentinterfaces.FileTypeViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; /** * Media content viewer for videos, sounds and images. */ -public class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { +class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName()); private AbstractFile lastFile; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index b6505544ba..b04473990c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +47,6 @@ import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; import org.sleuthkit.autopsy.casemodule.Case; -//import org.sleuthkit.autopsy.corecomponents.Bundle; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; @@ -61,7 +60,7 @@ import org.sleuthkit.datamodel.AbstractFile; @NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer", "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.", "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."}) -public class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel { +class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel { private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm()); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java index 8848164683..0a203a0909 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewVideoPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ import org.sleuthkit.datamodel.AbstractFile; * Video viewer part of the Media View layered pane. Uses different engines * depending on platform. */ -public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture, MediaFileViewer.MediaViewPanel { +abstract class MediaViewVideoPanel extends JPanel implements FrameCapture, MediaFileViewer.MediaViewPanel { private static final Set AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index ce2721e647..62b9996f22 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") public MessageContentViewer() { initComponents(); - drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", Node.EMPTY, 0, null); + drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null); attachmentsScrollPane.setViewportView(drp); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); @@ -716,20 +716,20 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @Override protected Sheet createSheet() { - Sheet s = new Sheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } AbstractFile file = getContent(); - ss.put(new NodeProperty<>("Name", "Name", "Name", file.getName())); - ss.put(new NodeProperty<>("Size", "Size", "Size", file.getSize())); - ss.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType()))); - ss.put(new NodeProperty<>("Known", "Known", "Known", file.getKnown().getName())); + sheetSet.put(new NodeProperty<>("Name", "Name", "Name", file.getName())); + sheetSet.put(new NodeProperty<>("Size", "Size", "Size", file.getSize())); + sheetSet.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType()))); + sheetSet.put(new NodeProperty<>("Known", "Known", "Known", file.getKnown().getName())); - addTagProperty(ss); - return s; + addTagProperty(sheetSet); + return sheet; } } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form index b9cd3d0fdd..b175b6b512 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form @@ -11,7 +11,7 @@ - + @@ -41,7 +41,8 @@ - + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java index 060280ed08..1bfa05d5b8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java @@ -59,9 +59,10 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer { jScrollPane2 = new javax.swing.JScrollPane(); jTextPane1 = new javax.swing.JTextPane(); - setPreferredSize(new java.awt.Dimension(610, 52)); + setPreferredSize(new java.awt.Dimension(100, 52)); - jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + jScrollPane2.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); jScrollPane2.setPreferredSize(new java.awt.Dimension(610, 52)); jTextPane1.setEditable(false); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PListRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PListRowFactory.java index 75b2970ecb..f0bc0a95bc 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/PListRowFactory.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PListRowFactory.java @@ -33,7 +33,8 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; /** * Factory class to create nodes for Plist table view */ -public class PListRowFactory extends ChildFactory { + +class PListRowFactory extends ChildFactory { private final List rows; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java index f3d630eaf3..6478114314 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java @@ -58,13 +58,12 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskCoreException; import org.xml.sax.SAXException; -import org.sleuthkit.autopsy.corecomponentinterfaces.FileTypeViewer; /** * PListViewer - a file viewer for binary plist files. * */ -public class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider { +class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider { private static final long serialVersionUID = 1L; private static final String[] MIMETYPES = new String[]{"application/x-bplist"}; @@ -192,7 +191,7 @@ public class PListViewer extends javax.swing.JPanel implements FileTypeViewer, E Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { JOptionPane.showMessageDialog(this, "Failed to export plist file.", diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java index 02a08f7037..afaf3ae398 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java @@ -34,7 +34,8 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; /** * Factory class to generate nodes for SQLite table rows */ -public class SQLiteTableRowFactory extends ChildFactory { + +class SQLiteTableRowFactory extends ChildFactory { private final List> rows; private final List colActions; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 4a4ee95a90..250b74f36a 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -50,13 +50,12 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.autopsy.corecomponentinterfaces.FileTypeViewer; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * A file content viewer for SQLite database files. */ -public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { +class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private static final long serialVersionUID = 1L; public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; @@ -320,7 +319,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { // Copy the file to temp folder String tmpDBPathName; try { - tmpDBPathName = Case.getOpenCase().getTempDirectory() + File.separator + sqliteDbFile.getName(); + tmpDBPathName = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + sqliteDbFile.getName(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_noCurrentCase()); @@ -373,7 +372,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { * @param metaFileName name of meta file to look for */ private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile, String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException { - Case openCase = Case.getOpenCase(); + Case openCase = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase(); Services services = new Services(sleuthkitCase); FileManager fileManager = services.getFileManager(); diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index f52ed088ae..6272cc9ab7 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -455,7 +455,8 @@ public final class CoordinationService { CASES("cases"), MANIFESTS("manifests"), CONFIG("config"), - CENTRAL_REPO("centralRepository"); + CENTRAL_REPO("centralRepository"), + HEALTH_MONITOR("healthMonitor"); private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 83250f719d..63ac880171 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -216,6 +216,7 @@ public class Installer extends ModuleInstall { packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 465529fa9f..342b58c4ee 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -77,6 +77,7 @@ + @@ -196,6 +197,10 @@ + + + + @@ -213,7 +218,7 @@ --> - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java index 191a851b9a..6af30b8729 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,41 +22,58 @@ import java.util.List; import org.openide.nodes.Node; /** - * The interface for the "top right component" window. + * An interface for result view components. A result view component provides + * multiple views of the application data represented by a given NetBeans Node. + * The differing views of the node are supplied by a collection of result + * viewers (implementations of the DataResultViewer interface). * + * A typical implementation of this interface are the NetBeans TopComponents + * (DataResultTopComponents) that use a child result view component + * (DataResultPanel) for displaying their result viewers, and are docked into + * the upper right hand side (editor mode) of the main application window. */ public interface DataResult { /** - * Sets the "selected" node in this class. + * Sets the node for which this result view component should provide + * multiple views of the underlying application data. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent on this result view + * component's result viewers. */ - public void setNode(Node selectedNode); + public void setNode(Node node); /** - * Gets the unique TopComponent ID of this class. + * Gets the preferred identifier for this result view panel in the window + * system. * - * @return preferredID the unique ID + * @return The preferred identifier. */ public String getPreferredID(); /** - * Sets the title of this TopComponent + * Sets the title of this result view component. * - * @param title the given title (String) + * @param title The title. */ public void setTitle(String title); /** - * Sets the descriptive context text at the top of the pane. + * Sets the descriptive text about the source of the nodes displayed in this + * result view component. * - * @param pathText Descriptive text giving context for the current results + * @param description The text to display. */ public void setPath(String pathText); /** - * Checks if this is the main (uncloseable) instance of DataResult + * Gets whether or not this result view panel is the "main" result view + * panel used to view the child nodes of a node selected in the application + * tree view (DirectoryTreeTopComponent) that is normally docked into the + * left hand side of the main window. * - * @return true if it is the main instance, otherwise false + * @return True or false. */ public boolean isMain(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java index d88c979844..c9d305f53e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,76 +22,120 @@ import java.awt.Component; import org.openide.nodes.Node; /** - * Interface for the different viewers that show a set of nodes in the - * DataResult area. AbstractDataResultViewer has default implementations for the - * action handlers. + * An interface for result viewers. A result viewer uses a Swing Component to + * provide a view of the application data represented by a NetBeans Node passed + * to it via its setNode method. * + * Result viewers are most commonly presented as a tab in a result view panel + * (DataResultPanel) inside a result view top component (DataResultTopComponent) + * that is docked into the upper right hand side (editor mode) of the main + * application window. + * + * A result viewer is typically a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view child component. Such a result + * viewer should use the explorer manager of the ancestor top component to + * connect the lookups of the nodes displayed in the NetBeans explorer view to + * the actions global context. It is strongly recommended that this type of + * result viewer is implemented by extending the abstract base class + * AbstractDataResultViewer, which will handle some key aspects of working with + * the ancestor top component's explorer manager. + * + * This interface is an extension point, so classes that implement it should + * provide an appropriate ServiceProvider annotation. */ public interface DataResultViewer { /** - * Set the root node to display in this viewer. When called with null, must - * clear all references to previous nodes. - */ - public void setNode(Node selectedNode); - - /** - * Gets the title of this viewer - */ - public String getTitle(); - - /** - * Get a new instance of DataResultViewer + * Creates a new instance of this result viewer, which allows the + * application to use the capability provided by this result viewer in more + * than one result view. This is done by using the default instance of this + * result viewer as a "factory" for creating other instances. */ public DataResultViewer createInstance(); /** - * Get the Swing component (i.e. JPanel) for this viewer + * Indicates whether this result viewer is able to provide a meaningful view + * of the application data represented by a given node. Typically, indicates + * whether or not this result viewer can use the given node as the root node + * of its child explorer view component. + * + * @param node The node. + * + * @return True or false. + */ + public boolean isSupported(Node node); + + /** + * Sets the node for which this result viewer should provide a view of the + * underlying application data. Typically, this means using the given node + * as the root node of this result viewer's child explorer view component. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent. + */ + public void setNode(Node node); + + /** + * Requests selection of the given child nodes of the node passed to + * setNode. This method should be implemented as a no-op for result viewers + * that do not display the child nodes of a given root node using a NetBeans + * explorer view set up to use a given explorer manager. + * + * @param selectedNodes The child nodes to select. + */ + default public void setSelectedNodes(Node[] selectedNodes) { + } + + /** + * Gets the title of this result viewer. + * + * @return The title. + */ + public String getTitle(); + + /** + * Gets the Swing component for this viewer. + * + * @return The component. */ public Component getComponent(); /** - * Resets the viewer. + * Resets the state of the Swing component for this viewer to its default + * state. */ - public void resetComponent(); + default public void resetComponent() { + } /** - * Frees the objects that have been allocated by this viewer, in preparation - * for permanently disposing of it. + * Frees any resources tha have been allocated by this result viewer, in + * preparation for permanently disposing of it. */ - public void clearComponent(); + default public void clearComponent() { + } /** - * Expand node, if supported by the viewed + * Sets the node for which this result viewer should provide a view of the + * underlying application data model object, and expands the node. * - * @param n Node to expand - */ - public void expandNode(Node n); - - /** - * Select the given node array - */ - public void setSelectedNodes(Node[] selected); - - /** - * Checks whether the currently selected root node is supported by this - * viewer + * @param node The node. * - * @param selectedNode the selected node - * - * @return True if supported, else false - */ - public boolean isSupported(Node selectedNode); - - /** - * Set a custom content viewer to respond to selection events from this - * result viewer. If not set, the default content viewer is used - * - * @param contentViewer content viewer to respond to selection events from - * this viewer - * - * @deprecated All implementations of this in the standard DataResultViewers are now no-ops. + * @deprecated This API is not used by the application. */ @Deprecated - public void setContentViewer(DataContent contentViewer); + default public void expandNode(Node node) { + } + + /** + * Sets a custom content viewer to which nodes selected in this result + * viewer should be pushed via DataContent.setNode. + * + * @param contentViewer The content viewer. + * + * @deprecated This API is not used by the application. + */ + @Deprecated + default public void setContentViewer(DataContent contentViewer) { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 26d58d6eff..fdabf16ac0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,65 +23,64 @@ import java.beans.PropertyVetoException; import java.util.logging.Level; import javax.swing.JPanel; import org.openide.explorer.ExplorerManager; -import org.openide.explorer.ExplorerManager.Provider; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; /** - * This class provides a default implementation of selected methods of the - * DataResultViewer interface. Derived classes will be Swing JPanel objects. - * Additionally, the ExplorerManager.Provider interface is implemented to supply - * an ExplorerManager to derived classes and their child components. + * An abstract base class for an implementation of the result viewer interface + * that is a JPanel that displays the child nodes of a given node using a + * NetBeans explorer view as a child component. Such a result viewer should use + * the explorer manager of an ancestor top component to connect the lookups of + * the nodes displayed in the NetBeans explorer view to the actions global + * context. This class handles some key aspects of working with the ancestor top + * component's explorer manager. + * + * Instances of this class can be supplied with the top component's explorer + * manager during construction, but the typical use case is for the result + * viewer to find the ancestor top component's explorer manager at runtime. + * + * IMPORTANT: If the result viewer is going to find the ancestor top component's + * explorer manager at runtime, the first call to the getExplorerManager method + * of this class must be made AFTER the component hierarchy is fully + * constructed. + * */ -abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider { +public abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); - protected transient ExplorerManager em; + private transient ExplorerManager explorerManager; /** - * This constructor is intended to allow an AbstractDataResultViewer to use - * an ExplorerManager provided by a TopComponent, allowing Node selections - * to be available to Actions via the action global context lookup when the - * TopComponent has focus. The ExplorerManager must be present when the - * object is constructed so that its child components can discover it using - * the ExplorerManager.find() method. + * Constructs an abstract base class for an implementation of the result + * viewer interface that is a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view as a child component. * - * @param explorerManager + * @param explorerManager The explorer manager to use in the NetBeans + * explorer view child component of this result + * viewer, may be null. If null, the explorer manager + * will be discovered the first time + * getExplorerManager is called. */ - AbstractDataResultViewer(ExplorerManager explorerManager) { - this.em = explorerManager; - } - - /** - * This constructor can be used by AbstractDataResultViewers that do not - * need to make Node selections available to Actions via the action global - * context lookup. - */ - public AbstractDataResultViewer() { - this(new ExplorerManager()); + public AbstractDataResultViewer(ExplorerManager explorerManager) { + this.explorerManager = explorerManager; } @Override - public void clearComponent() { - } - - public Node getSelectedNode() { - Node result = null; - Node[] selectedNodes = this.getExplorerManager().getSelectedNodes(); - if (selectedNodes.length > 0) { - result = selectedNodes[0]; + public ExplorerManager getExplorerManager() { + if (this.explorerManager == null) { + this.explorerManager = ExplorerManager.find(this); } - return result; + return this.explorerManager; } @Override - public void expandNode(Node n) { - } - - @Override - public void resetComponent() { + public void setSelectedNodes(Node[] selected) { + try { + this.getExplorerManager().setSelectedNodes(selected); + } catch (PropertyVetoException ex) { + logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS + } } @Override @@ -89,22 +88,4 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView return this; } - @Override - public ExplorerManager getExplorerManager() { - return this.em; - } - - @Override - public void setSelectedNodes(Node[] selected) { - try { - this.em.setSelectedNodes(selected); - } catch (PropertyVetoException ex) { - logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS - } - } - - @Deprecated - @Override - public void setContentViewer(DataContent contentViewer) { - } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 245fe1744d..bae8218148 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -26,7 +26,7 @@ LBL_Description=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2018.
URL_ON_IMG=http://www.sleuthkit.org/ -URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.6.0/ +URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.7.0/ FILE_FOR_LOCAL_HELP=file:/// INDEX_FOR_LOCAL_HELP=/docs/index.html LBL_Close=Close @@ -62,9 +62,6 @@ DataResultViewerThumbnail.filePathLabel.text=\ \ \ DataResultViewerThumbnail.goToPageLabel.text=Go to Page: DataResultViewerThumbnail.goToPageField.text= AdvancedConfigurationDialog.cancelButton.text=Cancel -DataResultPanel.directoryTablePath.text=directoryPath -DataResultPanel.numberMatchLabel.text=0 -DataResultPanel.matchLabel.text=Results DataContentViewerArtifact.waitText=Retrieving and preparing data, please wait... DataContentViewerArtifact.errorText=Error retrieving result DataContentViewerArtifact.title=Results @@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=
resultViewers = new ArrayList<>(); - private boolean isMain; - private ExplorerManager explorerManager; - private ExplorerManagerNodeSelectionListener emNodeSelectionListener; - private Node rootNode; - private final RootNodeListener rootNodeListener = new RootNodeListener(); - private boolean listeningToTabbedPane; + private final boolean isMain; + private final List resultViewers; + private final ExplorerManagerListener explorerManagerListener; + private final RootNodeListener rootNodeListener; private DataContent contentView; + private ExplorerManager explorerManager; + private Node currentRootNode; + private boolean listeningToTabbedPane; /** - * Constructs and opens a DataResultPanel with the given initial data, and - * the default DataContent. + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains instances of the result viewers (DataResultViewer) provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). The result view panel will push single node selections + * from its child result viewers to the "main" content view that is normally + * docked into the lower right hand side of the main application window. * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * - * @return A DataResultPanel instance. - */ - public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches) { - DataResultPanel resultPanel = new DataResultPanel(title, false); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); - resultPanel.open(); - return resultPanel; - } - - /** - * Constructs and opens a DataResultPanel with the given initial data, and a - * custom DataContent. - * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param customContentView A content view to use in place of the default - * content view. - * - * @return A DataResultPanel instance. - */ - public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { - DataResultPanel resultPanel = new DataResultPanel(title, customContentView); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); - resultPanel.open(); - return resultPanel; - } - - /** - * Constructs a DataResultPanel with the given initial data, and a custom - * DataContent. The panel is NOT opened; the client of this method must call - * open on the panel that is returned. - * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param customContentView A content view to use in place of the default - * content view. - * - * @return A DataResultPanel instance. - */ - public static DataResultPanel createInstanceUninitialized(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { - DataResultPanel resultPanel = new DataResultPanel(title, customContentView); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); - return resultPanel; - } - - /** - * Executes code common to all of the DataSreultPanel factory methods. - * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param resultViewPanel A content view to use in place of the default - * content view. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * + * @return A result view panel. */ - private static void createInstanceCommon(String title, String pathText, Node rootNode, int totalMatches, DataResultPanel resultViewPanel) { + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), DataContentTopComponent.findInstance()); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains a given collection of result viewers (DataResultViewer) instead + * of the result viewers provided by the results viewer extension point. The + * result view panel will push single node selections from its child result + * viewers to the "main" content view that is normally docked into the lower + * right hand side of the main application window. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results viewer + * extension point. + * + * @return A result view panel. + */ + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection viewers) { + DataResultPanel resultPanel = new DataResultPanel(title, false, viewers, DataContentTopComponent.findInstance()); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains instances of the result viewers (DataResultViewer) provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). The result view panel will push single node selections + * from its child result viewers to the supplied content view, which can be + * null if a content view is not needed. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param customContentView A custom content view to use instead of the + * "main" content view that is normally docked into + * the lower right hand side of the main + * application window. May be null, if no content + * view is needed. + * + * @return A result view panel. + */ + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates, but does not open, a Swing JPanel with a JTabbedPane child + * component that contains instances of the result viewers + * (DataResultViewer) provided by the result viewer extension point (service + * providers that implement DataResultViewer). The result view panel will + * push single node selections from its child result viewers to the supplied + * custom content view. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param customContentView A content view to use in place of the default + * content view. + * + * @return A result view panel. + */ + public static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + return resultPanel; + } + + /** + * Executes code common to all of the result view panel factory methods. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param resultViewPanel A new results view panel. + */ + private static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel) { resultViewPanel.setTitle(title); resultViewPanel.setName(title); - resultViewPanel.setNumMatches(totalMatches); - resultViewPanel.setNode(rootNode); - resultViewPanel.setPath(pathText); + resultViewPanel.setNumberOfChildNodes(childNodeCount); + resultViewPanel.setNode(currentRootNode); + resultViewPanel.setPath(description); } /** - * Constructs a DataResultPanel with the default DataContent + * Constructs a Swing JPanel with a JTabbedPane child component that + * contains a collection of result viewers that is either supplied or + * provided by the result viewer extension point. * - * @param title The title for the panel. - * @param isMain True if the DataResultPanel being constructed is the "main" - * DataResultPanel. + * @param title The title of the result view panel. + * @param isMain Whether or not the result view panel is the "main" + * instance of the panel that resides in the "main" + * results view (DataResultTopComponent) that is normally + * docked into the upper right hand side of the main + * application window. + * @param viewers A collection of result viewers to use instead of the + * result viewers provided by the results viewer + * extension point, may be empty. + * @param contentView A content view to into which to push single node + * selections in the child result viewers, may be null. */ - DataResultPanel(String title, boolean isMain) { - this(isMain, Lookup.getDefault().lookup(DataContent.class)); - setTitle(title); - } - - private DataResultPanel(boolean isMain, DataContent contentView) { + DataResultPanel(String title, boolean isMain, Collection viewers, DataContent contentView) { + this.setTitle(title); this.isMain = isMain; this.contentView = contentView; + this.resultViewers = new ArrayList<>(viewers); + this.explorerManagerListener = new ExplorerManagerListener(); + this.rootNodeListener = new RootNodeListener(); initComponents(); } /** - * Constructs a DataResultPanel with the a custom DataContent. - * - * @param title The title for the panel. - * @param customContentView A content view to use in place of the default - * content view. - */ - DataResultPanel(String title, DataContent customContentView) { - this(false, customContentView); - } - - /** - * Gets the preferred identifier for this panel in the window system. + * Gets the preferred identifier for this result view panel in the window + * system. * * @return The preferred identifier. */ @@ -200,19 +249,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Gets whether or not this panel is the "main" panel used to view the child - * nodes of a node selected in the tree view (DirectoryTreeTopComponent) - * that is normally docked into the left hand side of the main window. - * - * @return True or false. - */ - @Override - public boolean isMain() { - return this.isMain; - } - - /** - * Sets the title of this panel. + * Sets the title of this result view panel. * * @param title The title. */ @@ -223,27 +260,27 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C /** * Sets the descriptive text about the source of the nodes displayed in this - * panel. + * result view panel. * - * @param pathText The text to display. + * @param description The text to display. */ @Override - public void setPath(String pathText) { - this.directoryTablePath.setText(pathText); + public void setPath(String description) { + this.descriptionLabel.setText(description); } /** - * Adds a result viewer to this panel. + * Adds a results viewer to this result view panel. * - * @param resultViewer The result viewer. + * @param resultViewer The results viewer. */ public void addResultViewer(DataResultViewer resultViewer) { resultViewers.add(resultViewer); - dataResultTabbedPanel.addTab(resultViewer.getTitle(), resultViewer.getComponent()); + resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()); } /** - * Gets the result viewers for this panel. + * Gets the result viewers for this result view panel. * * @return A list of result viewers. */ @@ -253,8 +290,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Sets the content view for this panel. Needs to be called before the first - * call to open. + * Sets the content view for this result view panel. Needs to be called + * before the first call to open. * * @param customContentView A content view to use in place of the default * content view. @@ -264,67 +301,60 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Initializes this panel. Intended to be called by a parent top component + * Opens this result view panel. Should be called by a parent top component * when the top component is opened. */ public void open() { - if (null == explorerManager) { - /* - * Get an explorer manager to pass to the child result viewers. If - * the application components are put together as expected, this - * will be an explorer manager owned by a parent top component, and - * placed by the top component in the look up that is proxied as the - * action global context when the top component has focus. The - * sharing of this explorer manager enables the same child node - * selections to be made in all of the result viewers. - */ - explorerManager = ExplorerManager.find(this); - emNodeSelectionListener = new ExplorerManagerNodeSelectionListener(); - explorerManager.addPropertyChangeListener(emNodeSelectionListener); + /* + * The parent top component is expected to be an explorer manager + * provider that exposes a lookup maintained by its explorer manager to + * the actions global context. The child result view panel will then + * find the parent top component's explorer manager at runtime, so that + * it can act as an explorer manager provider for its child result + * viewers. This connects the nodes displayed in the result viewers to + * the actions global context. + */ + if (this.explorerManager == null) { + this.explorerManager = ExplorerManager.find(this); + this.explorerManager.addPropertyChangeListener(this.explorerManagerListener); } /* - * Load the child result viewers into the tabbed pane. + * Load either the supplied result viewers or the result viewers + * provided by the result viewer extension point into the tabbed pane. + * If loading from the extension point and distinct result viewer + * instances MUST be created if this is not the "main" result view. */ - if (0 == dataResultTabbedPanel.getTabCount()) { - /* - * TODO (JIRA-2658): Fix the DataResultViewer extension point. When - * this is done, restore the implementation of DataResultViewerTable - * and DataREsultViewerThumbnail as DataResultViewer service - * providers. - */ - addResultViewer(new DataResultViewerTable(this.explorerManager)); - addResultViewer(new DataResultViewerThumbnail(this.explorerManager)); - for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) { - DataResultViewer resultViewer; - if (isMain) { - resultViewer = factory; - } else { - resultViewer = factory.createInstance(); + if (this.resultViewerTabs.getTabCount() == 0) { + if (this.resultViewers.isEmpty()) { + for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) { + if (this.isMain) { + this.resultViewers.add(resultViewer); + } else { + this.resultViewers.add(resultViewer.createInstance()); + } } - addResultViewer(resultViewer); } - } - - if (isMain && null == rootNode) { - setNode(rootNode); + this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent())); } this.setVisible(true); } /** - * Sets the root node for this panel. The child nodes of the root node will - * be displayed in the result viewers. For the "main" panel, the root node - * is the currently selected node in the tree view docked into the left side - * of the main application window. + * Sets the current root node for this result view panel. The child nodes of + * the current root node will be displayed in the child result viewers. For + * the "main" panel, the root node is the currently selected node in the + * application tree view docked into the left side of the main application + * window. * - * @param rootNode The root node for this panel. + * @param rootNode The root node for this panel, may be null if the panel is + * to be reset. */ @Override public void setNode(Node rootNode) { - if (this.rootNode != null) { - this.rootNode.removeNodeListener(rootNodeListener); + if (this.currentRootNode != null) { + this.currentRootNode.removeNodeListener(rootNodeListener); } /* @@ -333,53 +363,54 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * construction. */ if (listeningToTabbedPane == false) { - dataResultTabbedPanel.addChangeListener(this); + resultViewerTabs.addChangeListener(this); listeningToTabbedPane = true; } - this.rootNode = rootNode; - if (this.rootNode != null) { + this.currentRootNode = rootNode; + if (this.currentRootNode != null) { rootNodeListener.reset(); - this.rootNode.addNodeListener(rootNodeListener); + this.currentRootNode.addNodeListener(rootNodeListener); } - resetTabs(this.rootNode); - setupTabs(this.rootNode); + this.resultViewers.forEach((viewer) -> { + viewer.resetComponent(); + }); + setupTabs(this.currentRootNode); - if (null != this.rootNode) { - int childrenCount = this.rootNode.getChildren().getNodesCount(); - this.numberMatchLabel.setText(Integer.toString(childrenCount)); + if (this.currentRootNode != null) { + int childrenCount = this.currentRootNode.getChildren().getNodesCount(); + this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount)); } - this.numberMatchLabel.setVisible(true); + this.numberOfChildNodesLabel.setVisible(true); } /** - * Gets the root node of this panel. For the "main" panel, the root node is - * the currently selected node in the tree view docked into the left side of - * the main application window. + * Gets the root node of this result view panel. For the "main" panel, the + * root node is the currently selected node in the application tree view + * docked into the left side of the main application window. * * @return The root node. */ public Node getRootNode() { - return rootNode; + return currentRootNode; } /** - * Set number of child nodes displayed for the current root node. + * Sets the label text that displays the number of the child nodes displayed + * by this result view panel's result viewers. * - * @param numberOfChildNodes + * @param numberOfChildNodes The number of child nodes. */ - public void setNumMatches(Integer numberOfChildNodes) { - if (this.numberMatchLabel != null) { - this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes)); - } + public void setNumberOfChildNodes(Integer numberOfChildNodes) { + this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes)); } /** - * Sets the children of the root node that should be currently selected in - * this panel's result viewers. + * Selects the given child nodes of the root node in this panel's result + * viewers. * - * @param selectedNodes The nodes to be selected. + * @param selectedNodes The child nodes to be selected. */ public void setSelectedNodes(Node[] selectedNodes) { this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); @@ -396,11 +427,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * Enable or disable the result viewer tabs based on whether or not the * corresponding results viewer supports display of the selected node. */ - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) { + for (int i = 0; i < resultViewerTabs.getTabCount(); i++) { if (resultViewers.get(i).isSupported(selectedNode)) { - dataResultTabbedPanel.setEnabledAt(i, true); + resultViewerTabs.setEnabledAt(i, true); } else { - dataResultTabbedPanel.setEnabledAt(i, false); + resultViewerTabs.setEnabledAt(i, false); } } @@ -415,17 +446,17 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { for (int i = 0; i < resultViewers.size(); ++i) { - if (resultViewers.get(i) instanceof DataResultViewerTable && dataResultTabbedPanel.isEnabledAt(i)) { + if (resultViewers.get(i) instanceof DataResultViewerTable && resultViewerTabs.isEnabledAt(i)) { tabToSelect = i; } } } }; - if (NO_TAB_SELECTED == tabToSelect) { - tabToSelect = dataResultTabbedPanel.getSelectedIndex(); - if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) { - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) { - if (dataResultTabbedPanel.isEnabledAt(i)) { + if (tabToSelect == NO_TAB_SELECTED) { + tabToSelect = resultViewerTabs.getSelectedIndex(); + if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) { + for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) { + if (resultViewerTabs.isEnabledAt(i)) { tabToSelect = i; break; } @@ -434,27 +465,15 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /* - * If there is a tab to sele3ct, do so, and push the selected node to - * the corresponding result viewer. + * If there is a tab to select, do so, and push the selected node to the + * corresponding result viewer. */ - if (NO_TAB_SELECTED != tabToSelect) { - dataResultTabbedPanel.setSelectedIndex(tabToSelect); + if (tabToSelect != NO_TAB_SELECTED) { + resultViewerTabs.setSelectedIndex(tabToSelect); resultViewers.get(tabToSelect).setNode(selectedNode); } } - /** - * Resets the state of the child result viewers, based on a selected root - * node. - * - * @param unusedSelectedNode The selected node. - */ - public void resetTabs(Node unusedSelectedNode) { - this.resultViewers.forEach((viewer) -> { - viewer.resetComponent(); - }); - } - /** * Responds to a tab selection changed event by setting the root node of the * corresponding result viewer. @@ -465,13 +484,13 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C public void stateChanged(ChangeEvent event) { JTabbedPane pane = (JTabbedPane) event.getSource(); int currentTab = pane.getSelectedIndex(); - if (-1 != currentTab) { + if (currentTab != DataResultPanel.NO_TAB_SELECTED) { DataResultViewer currentViewer = this.resultViewers.get(currentTab); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - currentViewer.setNode(rootNode); + currentViewer.setNode(currentRootNode); } finally { - this.setCursor(null); + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } @@ -489,7 +508,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C */ Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return true; } @@ -498,24 +517,22 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C /** * Closes down the component. Intended to be called by the parent top +>>>>>>> custom-release-may-2018 * component when it is closed. */ void close() { - if (null != explorerManager && null != emNodeSelectionListener) { - explorerManager.removePropertyChangeListener(emNodeSelectionListener); + if (explorerManager != null && explorerManagerListener != null) { + explorerManager.removePropertyChangeListener(explorerManagerListener); explorerManager = null; } this.resultViewers.forEach((viewer) -> viewer.setNode(null)); - if (!this.isMain) { + if (!this.isMain) { // RJCTODO: What? this.resultViewers.forEach(DataResultViewer::clearComponent); - this.directoryTablePath.removeAll(); - this.directoryTablePath = null; - this.numberMatchLabel.removeAll(); - this.numberMatchLabel = null; + this.descriptionLabel.removeAll(); + this.numberOfChildNodesLabel.removeAll(); this.matchLabel.removeAll(); - this.matchLabel = null; this.setLayout(null); this.removeAll(); this.setVisible(false); @@ -525,59 +542,70 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C @Override public ExplorerManager getExplorerManager() { return explorerManager; + } /** - * Responds to node selection change events from the explorer manager. + * Responds to node selection change events from the explorer manager of + * this panel's parent top component. The selected nodes are passed to the + * content view. This is how the results view and the content view are kept + * in sync. It is therefore required that all of the result viewers in this + * panel use the explorer manager of the parent top component. This supports + * this way of passing the selection to the content view, plus the exposure + * of the selection to through the actions global context, which is needed + * for multiple selection. */ - private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + private class ExplorerManagerListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { - try { - Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { - return; - } - - /* - * Only interested in node selection events. - */ - if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - if (contentView != null) { - Node[] selectedNodes = explorerManager.getSelectedNodes(); - - /* - * Pass the selected nodes to all of the result viewers - * sharing this explorer manager. - */ - resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); - - /* - * Passing null signals that either multiple nodes are - * selected, or no nodes are selected. This is important - * to the content view, since content views only work - * for a single node.. - */ - if (1 == selectedNodes.length) { - contentView.setNode(selectedNodes[0]); - } else { - contentView.setNode(null); - } - } - } finally { - setCursor(null); + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) { + /* + * Pass a single node selection in a result viewer to the + * content view. Note that passing null to the content view + * signals that either multiple nodes are selected, or a + * previous selection has been cleared. This is important to the + * content view, since its child content viewers only work for a + * single node. + */ + Node[] selectedNodes = explorerManager.getSelectedNodes(); + if (selectedNodes.length == 1) { + contentView.setNode(selectedNodes[0]); + } else { + contentView.setNode(null); } } } } + /** + * Worker for RootNodeListener childrenAdded. + */ + class SetupTabsChildrenWorker extends SwingWorker { + + private final Node childNode; + + SetupTabsChildrenWorker(Node aChildNode) { + childNode = aChildNode; + } + + @Override + protected Void doInBackground() throws Exception { + setupTabs(childNode); + return null; + } + + @Override + protected void done() { + setupTabs(childNode); + } + } + /** * Responds to changes in the root node due to asynchronous child node * creation. */ + // RJCTODO: Why do we need this? private class RootNodeListener implements NodeListener { private volatile boolean waitingForData = true; @@ -599,13 +627,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C */ if (waitingForData && containsReal(delta)) { waitingForData = false; - if (SwingUtilities.isEventDispatchThread()) { - setupTabs(nme.getNode()); - } else { - SwingUtilities.invokeLater(() -> { - setupTabs(nme.getNode()); - }); - } + Node childNode = nme.getNode(); + new SetupTabsChildrenWorker(childNode).execute(); } } @@ -623,8 +646,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * */ private void updateMatches() { - if (rootNode != null && rootNode.getChildren() != null) { - setNumMatches(rootNode.getChildren().getNodesCount()); + if (currentRootNode != null && currentRootNode.getChildren() != null) { + setNumMatches(currentRootNode.getChildren().getNodesCount()); } } @@ -655,52 +678,93 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C // //GEN-BEGIN:initComponents private void initComponents() { - directoryTablePath = new javax.swing.JLabel(); - numberMatchLabel = new javax.swing.JLabel(); + descriptionLabel = new javax.swing.JLabel(); + numberOfChildNodesLabel = new javax.swing.JLabel(); matchLabel = new javax.swing.JLabel(); - dataResultTabbedPanel = new javax.swing.JTabbedPane(); + resultViewerTabs = new javax.swing.JTabbedPane(); setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); - org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N - directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14)); + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N + descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14)); - org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N - dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5)); + resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(numberMatchLabel) + .addComponent(numberOfChildNodesLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(matchLabel)) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(numberMatchLabel) + .addComponent(numberOfChildNodesLabel) .addComponent(matchLabel)) - .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTabbedPane dataResultTabbedPanel; - private javax.swing.JLabel directoryTablePath; + private javax.swing.JLabel descriptionLabel; private javax.swing.JLabel matchLabel; - private javax.swing.JLabel numberMatchLabel; + private javax.swing.JLabel numberOfChildNodesLabel; + private javax.swing.JTabbedPane resultViewerTabs; // End of variables declaration//GEN-END:variables + /** + * Gets whether or not this result view panel is the "main" result view + * panel used to view the child nodes of a node selected in the application + * tree view (DirectoryTreeTopComponent) that is normally docked into the + * left hand side of the main window. + * + * @return True or false. + * + * @Deprecated This method has no valid use case. + */ + @Deprecated + @Override + public boolean isMain() { + return this.isMain; + } + + /** + * Sets the label text that displays the number of the child nodes displayed + * by this result view panel's result viewers. + * + * @param numberOfChildNodes The number of child nodes. + * + * @deprecated Use setNumberOfChildNodes instead. + */ + @Deprecated + public void setNumMatches(Integer numberOfChildNodes) { + this.setNumberOfChildNodes(numberOfChildNodes); + } + + /** + * Resets the state of this results panel. + * + * @param unusedSelectedNode Unused. + * + * @deprecated Use setNode(null) instead. + */ + @Deprecated + public void resetTabs(Node unusedSelectedNode) { + this.setNode(null); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form index cb4669a51e..755df0fcf0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form @@ -28,10 +28,10 @@ - + - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index 4a5d265a2c..dc88be9fee 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.corecomponents; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -39,201 +40,234 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Top component which displays results (top-right editor mode by default). + * A DataResultTopComponent object is a NetBeans top component that provides + * multiple views of the application data represented by a NetBeans Node. It is + * a result view component (implements DataResult) that contains a result view + * panel (DataResultPanel), which is also a result view component. The result + * view panel is a JPanel with a JTabbedPane child component that contains a + * collection of result viewers. Each result viewer (implements + * DataResultViewer) presents a different view of the current node. Result + * viewers are usually JPanels that display the child nodes of the current node + * using a NetBeans explorer view child component. The result viewers are either + * supplied during construction of the result view top component or provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). * - * There is a main tc instance that responds to directory tree selections. - * Others can also create an additional result viewer tc using one of the - * factory methods, that can be: + * Result view top components are typically docked into the upper right hand + * side of the main application window (editor mode), and are linked to the + * content view in the lower right hand side of the main application window + * (output mode) by the result view panel. The panel passes single node + * selections in the active result viewer to the content view. * - * - added to top-right corner as an additional, closeable viewer - added to a - * different, custom mode, - linked to a custom content viewer that responds to - * selections from this top component. + * The "main" result view top component receives its current node as a selection + * from the case tree view in the top component (DirectoryTreeYopComponent) + * docked into the left hand side of the main application window. * - * For embedding custom data result in other top components window, use - * DataResultPanel component instead, since we cannot nest top components. - * - * Encapsulates the internal DataResultPanel and delegates to it. - * - * Implements DataResult interface by delegating to the encapsulated - * DataResultPanel. + * Result view top components are explorer manager providers to connect the + * lookups of the nodes displayed in the NetBeans explorer views of the result + * viewers to the actions global context. */ @RetainLocation("editor") -public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { +public final class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); - private final ExplorerManager explorerManager = new ExplorerManager(); - private final DataResultPanel dataResultPanel; //embedded component with all the logic - private boolean isMain; - private String customModeName; - - //keep track of tcs opened for menu presenters private static final List activeComponentIds = Collections.synchronizedList(new ArrayList()); + private final boolean isMain; + private final String customModeName; + private final ExplorerManager explorerManager; + private final DataResultPanel dataResultPanel; /** - * Create a new data result top component + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into the upper right hand side of the main application window + * (editor mode) and will be linked to the content view in the lower right + * hand side of the main application window (output mode). Its result + * viewers are provided by the result viewer extension point (service + * providers that implement DataResultViewer). * - * @param isMain whether it is the main, application default result viewer, - * there can be only 1 main result viewer - * @param title title of the data result window + * @param title The title for the top component, appears on the top + * component's tab. + * @param description Descriptive text about the node displayed, appears + * on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * + * @return The result view top component. */ - public DataResultTopComponent(boolean isMain, String title) { - associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); - this.dataResultPanel = new DataResultPanel(title, isMain); - initComponents(); - customizeComponent(isMain, title); + public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), DataContentTopComponent.findInstance()); + initInstance(description, node, childNodeCount, resultViewTopComponent); + return resultViewTopComponent; + } + + /** + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into the upper right hand side of the main application window + * (editor mode) and will be linked to the content view in the lower right + * hand side of the main application window (output mode). + * + * @param title The title for the top component, appears on the top + * component's tab. + * @param description Descriptive text about the node displayed, appears + * on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results viewer + * extension point. + * + * @return The result view top component. + */ + public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount, Collection viewers) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, viewers, DataContentTopComponent.findInstance()); + initInstance(description, node, childNodeCount, resultViewTopComponent); + return resultViewTopComponent; } /** - * Create a new, custom data result top component, in addition to the - * application main one + * Creates a partially initialized result view top component that provides + * multiple views of the application data represented by a NetBeans Node. + * The result view will be docked into the upper right hand side of the main + * application window (editor mode) and will be linked to the content view + * in the lower right hand side of the main application window (output + * mode). Its result viewers are provided by the result viewer extension + * point (service providers that implement DataResultViewer). * - * @param name unique name of the data result window, also - * used as title - * @param mode custom mode to dock into - * @param customContentViewer custom content viewer to send selection events - * to + * IMPORTANT: Initialization MUST be completed by calling initInstance. + * + * @param title The title for the result view top component, appears on the + * top component's tab. + * + * @return The partially initialized result view top component. */ - DataResultTopComponent(String name, String mode, DataContentTopComponent customContentViewer) { + public static DataResultTopComponent createInstance(String title) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), DataContentTopComponent.findInstance()); + return resultViewTopComponent; + } + + /** + * Initializes a partially initialized result view top component. + * + * @param description Descriptive text about the node displayed, + * appears on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param resultViewTopComponent The partially initialized result view top + * component. + */ + public static void initInstance(String description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) { + resultViewTopComponent.setNumberOfChildNodes(childNodeCount); + resultViewTopComponent.open(); + resultViewTopComponent.setNode(node); + resultViewTopComponent.setPath(description); + resultViewTopComponent.requestActive(); + } + + /** + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into a custom mode and linked to the supplied content view. Its + * result viewers are provided by the result viewer extension point (service + * providers that implement DataResultViewer). + * + * @param title The title for the top component, appears + * on the top component's tab. + * @param mode The NetBeans Window system mode into which + * this top component should be docked. + * @param description Descriptive text about the node displayed, + * appears on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param contentViewTopComponent A content view to which this result view + * will be linked. + * + * @return The result view top component. + */ + public static DataResultTopComponent createInstance(String title, String mode, String description, Node node, int childNodeCount, DataContentTopComponent contentViewTopComponent) { + DataResultTopComponent newDataResult = new DataResultTopComponent(false, title, mode, Collections.emptyList(), contentViewTopComponent); + initInstance(description, node, childNodeCount, newDataResult); + return newDataResult; + } + + /** + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * the "main" result view and will docked into the upper right hand side of + * the main application window (editor mode) and will be linked to the + * content view in the lower right hand side of the main application window + * (output mode). Its result viewers are provided by the result viewer + * extension point (service providers that implement DataResultViewer). + * + * IMPORTANT: The "main" result view top component receives its current node + * as a selection from the case tree view in the top component + * (DirectoryTreeTopComponent) docked into the left hand side of the main + * application window. This constructor is RESERVED for the use of the + * DirectoryTreeTopComponent singleton only. DO NOT USE OTHERWISE. + * + * @param title The title for the top component, appears on the top + * component's tab. + */ + public DataResultTopComponent(String title) { + this(true, title, null, Collections.emptyList(), DataContentTopComponent.findInstance()); + } + + /** + * Constructs a result view top component that provides multiple views of + * the application data represented by a NetBeans Node. + * + * @param isMain Whether or not this is the "main" result + * view top component. + * @param title The title for the top component, appears + * on the top component's tab. + * @param mode The NetBeans Window system mode into which + * this top component should be docked. If + * null, the editor mode will be used by + * default. + * @param viewers A collection of result viewers. If empty, + * the result viewers provided by the results + * viewer extension point will be used. + * @param contentViewTopComponent A content view to which this result view + * will be linked, possibly null. + */ + private DataResultTopComponent(boolean isMain, String title, String mode, Collection viewers, DataContentTopComponent contentViewTopComponent) { + this.isMain = isMain; + this.explorerManager = new ExplorerManager(); associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); this.customModeName = mode; - dataResultPanel = new DataResultPanel(name, customContentViewer); + this.dataResultPanel = new DataResultPanel(title, isMain, viewers, contentViewTopComponent); initComponents(); - customizeComponent(isMain, name); + customizeComponent(title); } - private void customizeComponent(boolean isMain, String title) { - this.isMain = isMain; - this.customModeName = null; - - setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); - - setTitle(title); // set the title + private void customizeComponent(String title) { + setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); //NON-NLS + setTitle(title); setName(title); getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS - - putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI + putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); - activeComponentIds.add(title); } - /** - * Initialize previously created tc instance with additional data - * - * @param pathText - * @param givenNode - * @param totalMatches - * @param newDataResult previously created with createInstance() - * uninitialized instance - */ - public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) { - newDataResult.setNumMatches(totalMatches); - - newDataResult.open(); // open it first so the component can be initialized - - // set the tree table view - newDataResult.setNode(givenNode); - newDataResult.setPath(pathText); - - newDataResult.requestActive(); - } - - /** - * Creates a new non-default DataResult component and initializes it - * - * @param title Title of the component window - * @param pathText Descriptive text about the source of the nodes - * displayed - * @param givenNode The new root node - * @param totalMatches Cardinality of root node's children - * - * @return a new, not default, initialized DataResultTopComponent instance - */ - public static DataResultTopComponent createInstance(String title, String pathText, Node givenNode, int totalMatches) { - DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); - - initInstance(pathText, givenNode, totalMatches, newDataResult); - - return newDataResult; - } - - /** - * Creates a new non-default DataResult component linked with a custom data - * content, and initializes it. - * - * - * @param title Title of the component window - * @param mode custom mode to dock this custom TopComponent to - * @param pathText Descriptive text about the source of the nodes - * displayed - * @param givenNode The new root node - * @param totalMatches Cardinality of root node's children - * @param dataContentWindow a handle to data content top component window to - * - * @return a new, not default, initialized DataResultTopComponent instance - */ - public static DataResultTopComponent createInstance(String title, final String mode, String pathText, Node givenNode, int totalMatches, DataContentTopComponent dataContentWindow) { - DataResultTopComponent newDataResult = new DataResultTopComponent(title, mode, dataContentWindow); - - initInstance(pathText, givenNode, totalMatches, newDataResult); - return newDataResult; - } - - /** - * Creates a new non-default DataResult component. You probably want to use - * initInstance after it - * - * @param title - * - * @return a new, not default, not fully initialized DataResultTopComponent - * instance - */ - public static DataResultTopComponent createInstance(String title) { - final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); - - return newDataResult; - } - @Override public ExplorerManager getExplorerManager() { return explorerManager; } /** - * Get a list with names of active windows ids, e.g. for the menus + * Get a listing of the preferred identifiers of all the result view top + * components that have been created. * - * @return + * @return The listing. */ public static List getActiveComponentIds() { return new ArrayList<>(activeComponentIds); } - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - // //GEN-BEGIN:initComponents - private void initComponents() { - - org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel; - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE) - ); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - @Override public int getPersistenceType() { if (customModeName == null) { @@ -245,16 +279,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult, @Override public void open() { - setCustomMode(); - super.open(); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public List getViewers() { - return dataResultPanel.getViewers(); - } - - private void setCustomMode() { if (customModeName != null) { Mode mode = WindowManager.getDefault().findMode(customModeName); if (mode != null) { @@ -264,6 +288,12 @@ public class DataResultTopComponent extends TopComponent implements DataResult, logger.log(Level.WARNING, "Could not find mode: {0}, will dock into the default one", customModeName);//NON-NLS } } + super.open(); + } + + @Override + public List getViewers() { + return dataResultPanel.getViewers(); } @Override @@ -290,7 +320,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult, } else { selectedNode = null; } - + /* * If the selected node of the content viewer is different than that of * the result viewer, the content viewer needs to be updated. Otherwise, @@ -343,31 +373,15 @@ public class DataResultTopComponent extends TopComponent implements DataResult, @Override public boolean canClose() { - /* - * If this is the results top component in the upper right of the main - * window, only allow it to be closed when there's no case opened or no - * data sources in the open case. - */ Case openCase; try { - openCase = Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { + openCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException unused) { return true; } return (!this.isMain) || openCase.hasData() == false; } - /** - * Resets the tabs based on the selected Node. If the selected node is null - * or not supported, disable that tab as well. - * - * @param selectedNode the selected content Node - */ - public void resetTabs(Node selectedNode) { - - dataResultPanel.resetTabs(selectedNode); - } - public void setSelectedNodes(Node[] selected) { dataResultPanel.setSelectedNodes(selected); } @@ -376,7 +390,74 @@ public class DataResultTopComponent extends TopComponent implements DataResult, return dataResultPanel.getRootNode(); } - void setNumMatches(int matches) { - this.dataResultPanel.setNumMatches(matches); + /** + * Sets the cardinality of the current node's children + * + * @param childNodeCount The cardinality of the node's children. + */ + private void setNumberOfChildNodes(int childNodeCount) { + this.dataResultPanel.setNumberOfChildNodes(childNodeCount); } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel; + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + + /** + * Creates a partially initialized result view top component that provides + * multiple views of the application data represented by a NetBeans Node. + * The result view will be docked into the upper right hand side of the main + * application window (editor mode) and will be linked to the content view + * in the lower right hand side of the main application window (output + * mode). Its result viewers are provided by the result viewer extension + * point (service providers that implement DataResultViewer). + * + * IMPORTANT: Initialization MUST be completed by calling initInstance. + * + * @param isMain Ignored. + * @param title The title for the top component, appears on the top + * component's tab. + * + * @deprecated Use an appropriate overload of createIntance instead. + */ + @Deprecated + public DataResultTopComponent(boolean isMain, String title) { + this(false, title, null, Collections.emptyList(), DataContentTopComponent.findInstance()); + } + + /** + * Sets the node for which this result view component should provide + * multiple views of the underlying application data. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent on this result view + * component's result viewers. + * + * @deprecated Use setNode instead. + */ + @Deprecated + public void resetTabs(Node node) { + dataResultPanel.setNode(node); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index fe21a4ca55..1089bc62f9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -60,151 +60,153 @@ import org.openide.nodes.Node; import org.openide.nodes.Node.Property; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; /** - * A tabular viewer for the results view. + * A tabular result viewer that displays the children of the given root node + * using an OutlineView. * - * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, - * restore implementation of DataResultViewerTable as a DataResultViewer service - * provider. + * Instances of this class should use the explorer manager of an ancestor top + * component to connect the lookups of the nodes displayed in the OutlineView to + * the actions global context. The explorer manager can be supplied during + * construction, but the typical use case is for the result viewer to find the + * ancestor top component's explorer manager at runtime. */ -//@ServiceProvider(service = DataResultViewer.class) -public class DataResultViewerTable extends AbstractDataResultViewer { +@ServiceProvider(service = DataResultViewer.class) +public final class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); + private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName()); @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); - private static final Color TAGGED_COLOR = new Color(255, 255, 195); - + static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195); private final String title; + private final Map columnMap; + private final Map> propertiesMap; + private final Outline outline; + private final TableListener outlineViewListener; + private Node rootNode; /** - * The properties map: + * Constructs a tabular result viewer that displays the children of the + * given root node using an OutlineView. The viewer should have an ancestor + * top component to connect the lookups of the nodes displayed in the + * OutlineView to the actions global context. The explorer manager will be + * discovered at runtime. + */ + public DataResultViewerTable() { + this(null, Bundle.DataResultViewerTable_title()); + } + + /** + * Constructs a tabular result viewer that displays the children of a given + * root node using an OutlineView. The viewer should have an ancestor top + * component to connect the lookups of the nodes displayed in the + * OutlineView to the actions global context. * - * stored value of column index -> property at that index - * - * We move around stored values instead of directly using the column indices - * in order to not override settings for a column that may not appear in the - * current table view due to its collection of its children's properties. - */ - private final Map> propertiesMap = new TreeMap<>(); - - /** - * Stores references to the actual table column objects, keyed by column - * name, so that we can check there visibility later in - * storeColumnVisibility(). - */ - private final Map columnMap = new HashMap<>(); - - private Node currentRoot; - - /* - * Convience reference to internal Outline. - */ - private Outline outline; - - /** - * Listener for table model event and mouse clicks. - */ - private final TableListener tableListener; - - /** - * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions, and the default title. - * - * @param explorerManager allow for explorer manager sharing + * @param explorerManager The explorer manager of the ancestor top + * component. */ public DataResultViewerTable(ExplorerManager explorerManager) { this(explorerManager, Bundle.DataResultViewerTable_title()); } /** - * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions, and a custom title. + * Constructs a tabular result viewer that displays the children of a given + * root node using an OutlineView with a given title. The viewer should have + * an ancestor top component to connect the lookups of the nodes displayed + * in the OutlineView to the actions global context. * - * @param explorerManager allow for explorer manager sharing - * @param title The custom title. + * @param explorerManager The explorer manager of the ancestor top + * component. + * @param title The title. */ public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); this.title = title; - + this.columnMap = new HashMap<>(); + this.propertiesMap = new TreeMap<>(); + + /* + * Execute the code generated by the GUI builder. + */ initComponents(); - + + /* + * Configure the child OutlineView (explorer view) component. + */ outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); + outline = outlineView.getOutline(); + outline.setRowSelectionAllowed(true); + outline.setColumnSelectionAllowed(true); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - outline.setRootVisible(false); // don't show the root node + outline.setRootVisible(false); outline.setDragEnabled(false); outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); - // add a listener so that when columns are moved, the new order is stored - tableListener = new TableListener(); - outline.getColumnModel().addColumnModelListener(tableListener); - // the listener also moves columns back if user tries to move the first column out of place - outline.getTableHeader().addMouseListener(tableListener); + + /* + * Add a table listener to the child OutlineView (explorer view) to + * persist the order of the table columns when a column is moved. + */ + outlineViewListener = new TableListener(); + outline.getColumnModel().addColumnModelListener(outlineViewListener); + + /* + * Add a mouse listener to the child OutlineView (explorer view) to make + * sure the first column of the table is kept in place. + */ + outline.getTableHeader().addMouseListener(outlineViewListener); } /** - * Creates a DataResultViewerTable object that is NOT compatible with node - * multiple selection actions. - */ - public DataResultViewerTable() { - this(new ExplorerManager(),Bundle.DataResultViewerTable_title()); - } - - - /** - * Expand node + * Creates a new instance of a tabular result viewer that displays the + * children of a given root node using an OutlineView. This method exists to + * make it possible to use the default service provider instance of this + * class in the "main" results view of the application, while using distinct + * instances in other places in the UI. * - * @param n Node to expand + * @return A new instance of a tabular result viewer, */ @Override - public void expandNode(Node n) { - super.expandNode(n); - - outlineView.expandNode(n); + public DataResultViewer createInstance() { + return new DataResultViewerTable(); } /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. + * Gets the title of this tabular result viewer. */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) - ); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - private org.openide.explorer.view.OutlineView outlineView; - // End of variables declaration//GEN-END:variables - @Override - public boolean isSupported(Node selectedNode) { + @NbBundle.Messages("DataResultViewerTable.title=Table") + public String getTitle() { + return title; + } + + /** + * Indicates whether a given node is supported as a root node for this + * tabular viewer. + * + * @param candidateRootNode The candidate root node. + * + * @return + */ + @Override + public boolean isSupported(Node candidateRootNode) { return true; } + /** + * Sets the current root node of this tabular result viewer. + * + * @param rootNode The node to set as the current root node, possibly null. + */ @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void setNode(Node selectedNode) { - + public void setNode(Node rootNode) { /* * The quick filter must be reset because when determining column width, * ETable.getRowCount is called, and the documentation states that quick @@ -213,30 +215,30 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * model." */ outline.unsetQuickFilter(); - // change the cursor to "waiting cursor" for this operation + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - boolean hasChildren = false; - if (selectedNode != null) { - // @@@ This just did a DB round trip to get the count and the results were not saved... - hasChildren = selectedNode.getChildren().getNodesCount() > 0; - } - - if (hasChildren) { - currentRoot = selectedNode; - em.setRootContext(currentRoot); + /* + * If the given node is not null and has children, set it as the + * root context of the child OutlineView, otherwise make an + * "empty"node the root context. + * + * IMPORTANT NOTE: This is the first of many times where a + * getChildren call on the current root node causes all of the + * children of the root node to be created and defeats lazy child + * node creation, if it is enabled. It also likely leads to many + * case database round trips. + */ + if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { + this.rootNode = rootNode; + this.getExplorerManager().setRootContext(this.rootNode); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); // make empty node + this.getExplorerManager().setRootContext(emptyNode); outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - - /* - * Since we are modifying the columns, we don't want to listen - * to added/removed events as un-hide/hide. - */ - tableListener.listenToVisibilityChanges(false); - outlineView.setPropertyColumns(); // set the empty property header + outlineViewListener.listenToVisibilityChanges(false); + outlineView.setPropertyColumns(); } } finally { this.setCursor(null); @@ -244,17 +246,17 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Create Column Headers based on the Content represented by the Nodes in - * the table. Load persisted column order, sorting and visibility. + * Sets up the Outline view of this tabular result viewer by creating + * column headers based on the children of the current root node. The + * persisted column order, sorting and visibility is used. */ private void setupTable() { /* * Since we are modifying the columns, we don't want to listen to * added/removed events as un-hide/hide, until the table setup is done. */ - tableListener.listenToVisibilityChanges(false); - - /** + outlineViewListener.listenToVisibilityChanges(false); + /* * OutlineView makes the first column be the result of * node.getDisplayName with the icon. This duplicates our first column, * which is the file name, etc. So, pop that property off the list, but @@ -286,7 +288,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { setColumnWidths(); - //Load column sorting information from preferences file and apply it to columns. + /* + * Load column sorting information from preferences file and apply it to + * columns. + */ loadColumnSorting(); /* @@ -298,7 +303,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ populateColumnMap(); - //Load column visibility information from preferences file and apply it to columns. + /* + * Load column visibility information from preferences file and apply it + * to columns. + */ loadColumnVisibility(); /* @@ -306,32 +314,36 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * it. */ SwingUtilities.invokeLater(() -> { - if (currentRoot instanceof TableFilterNode) { - NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo(); + if (rootNode instanceof TableFilterNode) { + NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { - Node[] childNodes = currentRoot.getChildren().getNodes(true); + Node[] childNodes = rootNode.getChildren().getNodes(true); for (int i = 0; i < childNodes.length; ++i) { Node childNode = childNodes[i]; if (selectedChildInfo.matches(childNode)) { try { - em.setSelectedNodes(new Node[]{childNode}); + this.getExplorerManager().setSelectedNodes(new Node[]{childNode}); } catch (PropertyVetoException ex) { - logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); + LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); } break; } } - ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null); + ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null); } } }); - //the table setup is done, so any added/removed events can now be treated as un-hide/hide. - tableListener.listenToVisibilityChanges(true); + /* + * The table setup is done, so any added/removed events can now be + * treated as un-hide/hide. + */ + outlineViewListener.listenToVisibilityChanges(true); } /* - * Populate the map with references to the column objects for use when + * Populates the column map for the child OutlineView of this tabular + * result viewer with references to the column objects for use when * loading/storing the visibility info. */ private void populateColumnMap() { @@ -348,8 +360,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } } + /* + * Sets the column widths for the child OutlineView of this tabular results + * viewer. + */ private void setColumnWidths() { - if (currentRoot.getChildren().getNodesCount() != 0) { + if (rootNode.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); if (graphics != null) { final FontMetrics metrics = graphics.getFontMetrics(); @@ -385,8 +401,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } } + /* + * Sets up the columns for the child OutlineView of this tabular results + * viewer with respect to column names and visisbility. + */ synchronized private void assignColumns(List> props) { - // Get the columns setup with respect to names and sortability String[] propStrings = new String[props.size() * 2]; for (int i = 0; i < props.size(); i++) { final Property prop = props.get(i); @@ -399,29 +418,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer { propStrings[2 * i] = prop.getName(); propStrings[2 * i + 1] = prop.getDisplayName(); } - outlineView.setPropertyColumns(propStrings); } /** - * Store the current column visibility information into a preference file. + * Persists the current column visibility information for the child + * OutlineView of this tabular result viewer using a preferences file. */ private synchronized void storeColumnVisibility() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRoot; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); - - //store hidden state for (Map.Entry entry : columnMap.entrySet()) { - String columnName = entry.getKey(); final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName); final TableColumn column = entry.getValue(); - boolean columnHidden = columnModel.isColumnHidden(column); if (columnHidden) { preferences.putBoolean(columnHiddenKey, true); @@ -433,16 +448,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Store the current column order information into a preference file. + * Persists the current column ordering for the child OutlineView of this + * tabular result viewer using a preferences file. */ private synchronized void storeColumnOrder() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRoot; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - // Store the current order of the columns into settings for (Map.Entry> entry : propertiesMap.entrySet()) { preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey()); @@ -451,20 +466,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Store the current column sorting information into a preference file. + * Persists the current column sorting information using a preferences file. */ private synchronized void storeColumnSorting() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = ((TableFilterNode) rootNode); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry entry : columnMap.entrySet()) { ETableColumn etc = entry.getValue(); String columnName = entry.getKey(); - //store sort rank and order final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName); final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName); @@ -482,47 +496,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Reads and applies the column sorting information persisted to the - * preferences file. Must be called after loadColumnOrder() since it depends - * on propertiesMap being initialized, and after assignColumns since it - * cannot set the sort on columns that have not been added to the table. + * preferences file. Must be called after loadColumnOrder, since it depends + * on the properties map being initialized, and after assignColumns, since + * it cannot set the sort on columns that have not been added to the table. */ private synchronized void loadColumnSorting() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - - if (currentRoot instanceof TableFilterNode) { - final TableFilterNode tfn = (TableFilterNode) currentRoot; - + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); //organize property sorting information, sorted by rank TreeSet sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); propertiesMap.entrySet().stream().forEach(entry -> { final String propName = entry.getValue().getName(); //if the sort rank is undefined, it will be defaulted to 0 => unsorted. - Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0); //default to true => ascending Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true); - sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); }); - //apply sort information in rank order. sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); } } + /** + * Reads and applies the column visibility information persisted to the + * preferences file. + */ private synchronized void loadColumnVisibility() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - - if (currentRoot instanceof TableFilterNode) { - + if (rootNode instanceof TableFilterNode) { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + final TableFilterNode tfn = ((TableFilterNode) rootNode); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry> entry : propertiesMap.entrySet()) { final String propName = entry.getValue().getName(); @@ -535,7 +545,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Gets a list of child properties (columns) in the order persisted in the - * preference file. Also initialized the propertiesMap with the column + * preference file. Also initialized the properties map with the column * order. * * @return a List> of the properties in the persisted @@ -543,14 +553,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ private synchronized List> loadColumnOrder() { - List> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100); + List> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100); // If node is not table filter node, use default order for columns - if (!(currentRoot instanceof TableFilterNode)) { + if (!(rootNode instanceof TableFilterNode)) { return props; } - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + final TableFilterNode tfn = ((TableFilterNode) rootNode); propertiesMap.clear(); /* @@ -587,24 +597,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return new ArrayList<>(propertiesMap.values()); } - @Override - @NbBundle.Messages("DataResultViewerTable.title=Table") - public String getTitle() { - return title; - } - - @Override - public DataResultViewer createInstance() { - return new DataResultViewerTable(); - } - + /** + * Frees the resources that have been allocated by this tabular results + * viewer, in preparation for permanently disposing of it. + */ @Override public void clearComponent() { this.outlineView.removeAll(); this.outlineView = null; - super.clearComponent(); - } /** @@ -775,8 +776,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); // only override the color if a node is not selected - if (currentRoot != null && !isSelected) { - Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); + if (rootNode != null && !isSelected) { + Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row)); boolean tagFound = false; if (node != null) { Node.PropertySet[] propSets = node.getPropertySets(); @@ -796,10 +797,37 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } //if the node does have associated tags, set its background color if (tagFound) { - component.setBackground(TAGGED_COLOR); + component.setBackground(TAGGED_ROW_COLOR); } } return component; } } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 6d5cd54f5b..c3f1d19b64 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,7 @@ import org.openide.nodes.NodeMemberEvent; import org.openide.nodes.NodeReorderEvent; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; @@ -59,64 +60,67 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; /** - * A thumbnail viewer for the results view, with paging support. + * A thumbnail result viewer, with paging support, that displays the children of + * the given root node using an IconView. The paging is intended to reduce + * memory footprint by loading no more than two humdred images at a time. * - * The paging is intended to reduce memory footprint by load only up to - * (currently) 200 images at a time. This works whether or not the underlying - * content nodes are being lazy loaded or not. - * - * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, - * restore implementation of DataResultViewerTable as a DataResultViewer service - * provider. + * Instances of this class should use the explorer manager of an ancestor top + * component to connect the lookups of the nodes displayed in the IconView to + * the actions global context. The explorer manager can be supplied during + * construction, but the typical use case is for the result viewer to find the + * ancestor top component's explorer manager at runtime. */ -//@ServiceProvider(service = DataResultViewer.class) -final class DataResultViewerThumbnail extends AbstractDataResultViewer { +@ServiceProvider(service = DataResultViewer.class) +public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); - private int curPage; - private int totalPages; - private int curPageImages; - private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; private final PageUpdater pageUpdater = new PageUpdater(); - private TableFilterNode tfn; - private ThumbnailViewChildren tvc; + private TableFilterNode rootNode; + private ThumbnailViewChildren rootNodeChildren; + private NodeSelectionListener selectionListener; + private int currentPage; + private int totalPages; + private int currentPageImages; + private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is compatible with node multiple selection actions. + * Constructs a thumbnail result viewer, with paging support, that displays + * the children of the given root node using an IconView. The viewer should + * have an ancestor top component to connect the lookups of the nodes + * displayed in the IconView to the actions global context. The explorer + * manager will be discovered at runtime. + */ + public DataResultViewerThumbnail() { + this(null); + } + + /** + * Constructs a thumbnail result viewer, with paging support, that displays + * the children of the given root node using an IconView. The viewer should + * have an ancestor top component to connect the lookups of the nodes + * displayed in the IconView to the actions global context. * - * @param explorerManager The shared ExplorerManager for the result viewers. + * @param explorerManager The explorer manager of the ancestor top + * component. */ - DataResultViewerThumbnail(ExplorerManager explorerManager) { - super(explorerManager); - initialize(); - } - - /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is NOT compatible with node multiple selection actions. - */ - DataResultViewerThumbnail() { - initialize(); - } - - @NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", + @NbBundle.Messages({ + "DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails" }) - private void initialize() { + public DataResultViewerThumbnail(ExplorerManager explorerManager) { + super(explorerManager); initComponents(); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); - thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( - new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), - Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), - Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); + thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{ + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); thumbnailSizeComboBox.setSelectedIndex(1); - curPage = -1; + currentPage = -1; totalPages = 0; - curPageImages = 0; + currentPageImages = 0; } /** @@ -297,24 +301,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (thumbSize != newIconSize) { thumbSize = newIconSize; - Node root = em.getRootContext(); + Node root = this.getExplorerManager().getRootContext(); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); - - // Temporarily set the explored context to the root, instead of a child node. // This is a workaround hack to convince org.openide.explorer.ExplorerManager to // update even though the new and old Node values are identical. This in turn // will cause the entire view to update completely. After this we // immediately set the node back to the current child by calling switchPage(). - em.setExploredContext(root); + this.getExplorerManager().setExploredContext(root); switchPage(); } }//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed - List> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100); - SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); + List> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100); + SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode)); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); createDialog.setVisible(true); @@ -335,8 +337,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { Node.Property prop = childProperties.get(i); String propName = prop.getName(); SortCriterion criterion = criteriaMap.get(prop); - final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName); - final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName); + final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName); + final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName); if (criterion != null) { preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING); @@ -346,7 +348,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { preferences.remove(columnSortRankKey); } } - setNode(tfn); //this is just to force a refresh + setNode(rootNode); //this is just to force a refresh } }//GEN-LAST:event_sortButtonActionPerformed @@ -379,28 +381,31 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - if (tvc != null) { - tvc.cancelLoadingThumbnails(); + if (selectionListener == null) { + this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup + } + if (rootNodeChildren != null) { + rootNodeChildren.cancelLoadingThumbnails(); } try { if (givenNode != null) { - tfn = (TableFilterNode) givenNode; + rootNode = (TableFilterNode) givenNode; /* * Wrap the given node in a ThumbnailViewChildren that will * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - tvc = new ThumbnailViewChildren(givenNode,thumbSize); - final Node root = new AbstractNode(tvc); + rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize); + final Node root = new AbstractNode(rootNodeChildren); pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); - em.setRootContext(root); + this.getExplorerManager().setRootContext(root); } else { - tfn = null; - tvc = null; + rootNode = null; + rootNodeChildren = null; Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); + this.getExplorerManager().setRootContext(emptyNode); iconView.setBackground(Color.BLACK); } } finally { @@ -422,8 +427,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { public void resetComponent() { super.resetComponent(); this.totalPages = 0; - this.curPage = -1; - curPageImages = 0; + this.currentPage = -1; + currentPageImages = 0; updateControls(); } @@ -435,15 +440,15 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } private void nextPage() { - if (curPage < totalPages) { - curPage++; + if (currentPage < totalPages) { + currentPage++; switchPage(); } } private void previousPage() { - if (curPage > 1) { - curPage--; + if (currentPage > 1) { + currentPage--; switchPage(); } } @@ -465,7 +470,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { return; } - curPage = newPage; + currentPage = newPage; switchPage(); } @@ -488,10 +493,11 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); progress.start(); progress.switchToIndeterminate(); - Node root = em.getRootContext(); - Node pageNode = root.getChildren().getNodeAt(curPage - 1); - em.setExploredContext(pageNode); - curPageImages = pageNode.getChildren().getNodesCount(); + ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager(); + Node root = explorerManager.getRootContext(); + Node pageNode = root.getChildren().getNodeAt(currentPage - 1); + explorerManager.setExploredContext(pageNode); + currentPageImages = pageNode.getChildren().getNodesCount(); return null; } @@ -504,15 +510,16 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { try { get(); } catch (InterruptedException | ExecutionException ex) { - NotifyDescriptor d = - new NotifyDescriptor.Message( + NotifyDescriptor d + = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", ex.getMessage()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(d); logger.log(Level.SEVERE, "Error making thumbnails: {0}", ex.getMessage()); //NON-NLS - } // catch and ignore if we were cancelled + } catch (java.util.concurrent.CancellationException ex) { + // catch and ignore if we were cancelled } } }.execute(); @@ -534,20 +541,19 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { sortLabel.setText(DataResultViewerThumbnail_sortLabel_text()); } else { - pageNumLabel.setText( - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", - Integer.toString(curPage), Integer.toString(totalPages))); - final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; - final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; + pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", + Integer.toString(currentPage), Integer.toString(totalPages))); + final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; + final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); - pageNextButton.setEnabled(!(curPage == totalPages)); - pagePrevButton.setEnabled(!(curPage == 1)); + pageNextButton.setEnabled(!(currentPage == totalPages)); + pagePrevButton.setEnabled(!(currentPage == 1)); goToPageField.setEnabled(totalPages > 1); sortButton.setEnabled(true); thumbnailSizeComboBox.setEnabled(true); - if (tfn != null) { - String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream() + if (rootNode != null) { + String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream() .map(SortCriterion::toString) .collect(Collectors.joining(" ")); sortString = StringUtils.defaultIfBlank(sortString, "---"); @@ -578,30 +584,30 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { totalPages = root.getChildren().getNodesCount(); if (totalPages == 0) { - curPage = -1; + currentPage = -1; updateControls(); return; } - if (curPage == -1 || curPage > totalPages) { - curPage = 1; + if (currentPage == -1 || currentPage > totalPages) { + currentPage = 1; } //force load the curPage node - final Node pageNode = root.getChildren().getNodeAt(curPage - 1); + final Node pageNode = root.getChildren().getNodeAt(currentPage - 1); //em.setSelectedNodes(new Node[]{pageNode}); if (pageNode != null) { pageNode.addNodeListener(new NodeListener() { @Override public void childrenAdded(NodeMemberEvent nme) { - curPageImages = pageNode.getChildren().getNodesCount(); + currentPageImages = pageNode.getChildren().getNodesCount(); updateControls(); } @Override public void childrenRemoved(NodeMemberEvent nme) { - curPageImages = 0; + currentPageImages = 0; updateControls(); } @@ -618,7 +624,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } }); - em.setExploredContext(pageNode); + DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode); } updateControls(); @@ -627,7 +633,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public void childrenRemoved(NodeMemberEvent nme) { totalPages = 0; - curPage = -1; + currentPage = -1; updateControls(); } @@ -640,14 +646,14 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } } - private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + private class NodeSelectionListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - Node[] selectedNodes = em.getSelectedNodes(); + Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes(); if (selectedNodes.length == 1) { AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); if (af == null) { @@ -670,5 +676,4 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } } } - } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java index 54f2a358b5..5bcd68a03b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java @@ -76,7 +76,8 @@ public class Installer extends ModuleInstall { private void setLookAndFeel() { if (System.getProperty("os.name").toLowerCase().contains("mac")) { //NON-NLS - setOSXLookAndFeel(); + setUnixLookAndFeel(); + setModuleSettings("false"); }else if (System.getProperty("os.name").toLowerCase().contains("nux")){ setUnixLookAndFeel(); } @@ -120,7 +121,7 @@ public class Installer extends ModuleInstall { }); } - public static void setUIFont (javax.swing.plaf.FontUIResource f){ + private static void setUIFont (javax.swing.plaf.FontUIResource f){ java.util.Enumeration keys = UIManager.getDefaults().keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java index 6226b3e58b..7766098437 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java @@ -45,6 +45,7 @@ class TableFilterChildren extends FilterNode.Children { * TableFilterNode. */ public static Children createInstance(Node wrappedNode, boolean createChildren) { + if (createChildren) { return new TableFilterChildren(wrappedNode); } else { @@ -63,7 +64,7 @@ class TableFilterChildren extends FilterNode.Children { TableFilterChildren(Node wrappedNode) { super(wrappedNode); } - + /** * Copies a TableFilterNode, with the create children (child factory) flag * set to false. diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java new file mode 100644 index 0000000000..5a5c865bfc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java @@ -0,0 +1,47 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import org.openide.nodes.Children; +import org.openide.nodes.Node; + +/** + * Provides TableFilterChildren functionality, and adds support for children + * of rows (plus/minus buttons for each row with children). + */ +final class TableFilterChildrenWithDescendants extends TableFilterChildren { + + private TableFilterChildrenWithDescendants(Node wrappedNode) { + super(wrappedNode); + } + + public static Children createInstance(Node wrappedNode, boolean createChildren){ + if(createChildren){ + return new TableFilterChildrenWithDescendants(wrappedNode); + } else { + return Children.LEAF; + } + } + + @Override + protected Node copyNode(Node nodeToCopy){ + return new TableFilterNode(nodeToCopy, true, true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java index 2541c11277..879f625b6e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; public class TableFilterNode extends FilterNode { private final boolean createChildren; + private boolean forceUseWrappedDisplayName = false; private String columnOrderKey = "NONE"; /** @@ -41,27 +42,53 @@ public class TableFilterNode extends FilterNode { * ensure the individual viewers display only the first layer of child * nodes. * - * @param node The node to wrap in the filter node. + * @param node The node to wrap in the filter node. * @param createChildren True if a Children object should be created for the - * wrapped node. + * wrapped node. */ public TableFilterNode(Node node, boolean createChildren) { super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node)); this.createChildren = createChildren; } + /** + * Constructs a filter node that generates children using + * TableFilterChildrenWithDescendants. This enables row to have descendants. + * + * Enables use of getDisplayName() for children of this node. + * + * @param node The node to wrap + */ + public TableFilterNode(Node node) { + super(node, TableFilterChildrenWithDescendants.createInstance(node, true), Lookups.proxy(node)); + this.createChildren = true; + this.forceUseWrappedDisplayName = false; + } + + /** + * To be used in TableFilterChildrenWithDescendants. + * + * @param node node to wrap + * @param createChildren node has children? + * @param forceUseWrappedDisplayName allow use of custom getDisplayName() . + */ + TableFilterNode(Node node, boolean createChildren, boolean forceUseWrappedDisplayName) { + super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node)); + this.createChildren = createChildren; + this.forceUseWrappedDisplayName = forceUseWrappedDisplayName; + } + /** * Constructs a filter node that creates at most one layer of child nodes * for the node it wraps. It is designed to be used in the results view to * ensure the individual viewers display only the first layer of child * nodes. * - * @param node The node to wrap in the filter node. + * @param node The node to wrap in the filter node. * @param createChildren True if a Children object should be created for the - * wrapped node. + * wrapped node. * @param columnOrderKey A key that represents the type of the original - * wrapped node and what is being displayed under that - * node. + * wrapped node and what is being displayed under that node. */ public TableFilterNode(Node node, boolean createChildren, String columnOrderKey) { super(node, TableFilterChildren.createInstance(node, createChildren)); @@ -77,13 +104,19 @@ public class TableFilterNode extends FilterNode { */ @Override public String getDisplayName() { - if (createChildren) { + if (this.forceUseWrappedDisplayName) { + return super.getDisplayName(); + } else if (createChildren) { return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text"); } else { return super.getDisplayName(); } } + protected String getParentDisplayName() { + return super.getDisplayName(); + } + /** * Adds information about which child node of this node, if any, should be * selected. Can be null. @@ -105,7 +138,7 @@ public class TableFilterNode extends FilterNode { * selected. * * @return The child node selection information, or null if no child should - * be selected. + * be selected. */ public NodeSelectionInfo getChildNodeSelectionInfo() { /* @@ -121,10 +154,10 @@ public class TableFilterNode extends FilterNode { /** * @return the column order key, which allows custom column ordering to be - * written into a properties file and be reloaded for future use in - * a table with the same root node or for different cases. This is - * done by DataResultViewerTable. The key should represent what - * kinds of items the table is showing. + * written into a properties file and be reloaded for future use in a table + * with the same root node or for different cases. This is done by + * DataResultViewerTable. The key should represent what kinds of items the + * table is showing. */ String getColumnOrderKey() { return columnOrderKey; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 2e517db0bb..11e92836ac 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -132,8 +132,7 @@ public class ImageUtils { } catch (UnsatisfiedLinkError e) { openCVLoadedTemp = false; LOGGER.log(Level.SEVERE, "OpenCV Native code library failed to load", e); //NON-NLS - //TODO: show warning bubble - + MessageNotifyUtil.Notify.show("Open CV", "OpenCV native library failed to load, see log for more details", MessageNotifyUtil.MessageType.WARNING); } OPEN_CV_LOADED = openCVLoadedTemp; @@ -385,7 +384,7 @@ public class ImageUtils { private static File getCachedThumbnailLocation(long fileID) { return cacheFileMap.computeIfAbsent(fileID, id -> { try { - String cacheDirectory = Case.getOpenCase().getCacheDirectory(); + String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory(); return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS } catch (NoCurrentCaseException e) { LOGGER.log(Level.WARNING, "Could not get cached thumbnail location. No case is open."); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java index b715d8ce35..7d66dc4e3c 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java @@ -358,7 +358,7 @@ public class PlatformUtil { File[] files = dev.listFiles(); for (File f : files) { String name = f.getName(); - if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 3) { //NON-NLS + if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 5) { //NON-NLS String path = "/dev/" + name; //NON-NLS if (canReadDrive(path)) { try { @@ -401,7 +401,7 @@ public class PlatformUtil { File[] files = dev.listFiles(); for (File f : files) { String name = f.getName(); - if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 4) { //NON-NLS + if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 7) { //NON-NLS String path = "/dev/" + name; //NON-NLS if (canReadDrive(path)) { drives.add(new LocalDisk(path, path, f.getTotalSpace())); diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java index 59d16ac5f3..1ff258dade 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -45,8 +45,8 @@ import org.sleuthkit.datamodel.AbstractFile; */ public class VideoUtils { - private static final List SUPPORTED_VIDEO_EXTENSIONS = - Arrays.asList("mov", "m4v", "flv", "mp4", "3gp", "avi", "mpg", //NON-NLS + private static final List SUPPORTED_VIDEO_EXTENSIONS + = Arrays.asList("mov", "m4v", "flv", "mp4", "3gp", "avi", "mpg", //NON-NLS "mpeg", "asf", "divx", "rm", "moov", "wmv", "vob", "dat", //NON-NLS "m1v", "m2v", "m4v", "mkv", "mpe", "yop", "vqa", "xmv", //NON-NLS "mve", "wtv", "webm", "vivo", "vc1", "seq", "thp", "san", //NON-NLS @@ -91,8 +91,17 @@ public class VideoUtils { private VideoUtils() { } - public static File getTempVideoFile(AbstractFile file) throws NoCurrentCaseException { - return Paths.get(Case.getOpenCase().getTempDirectory(), "videos", file.getId() + "." + file.getNameExtension()).toFile(); //NON-NLS + /** + * Gets a File object in the temp directory of the current case for the + * given AbstractFile object. + * + * @param file The AbstractFile object + * + * @return The File object + * + */ + public static File getVideoFileInTempDir(AbstractFile file) throws NoCurrentCaseException { + return Paths.get(Case.getCurrentCaseThrows().getTempDirectory(), "videos", file.getId() + "." + file.getNameExtension()).toFile(); //NON-NLS } public static boolean isVideoThumbnailSupported(AbstractFile file) { @@ -104,7 +113,7 @@ public class VideoUtils { static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { java.io.File tempFile; try { - tempFile = getTempVideoFile(file); + tempFile = getVideoFileInTempDir(file); } catch (NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS return null; @@ -189,4 +198,25 @@ public class VideoUtils { } return bufferedImage == null ? null : ScalrWrapper.resizeFast(bufferedImage, iconSize); } + + /** + * Gets a File object in the temp directory of the current case for the + * given AbstractFile object. + * + * @param file The AbstractFile object + * + * @return The File object + * + * @deprecated Call getVideoFileInTempDir instead. + */ + @Deprecated + public static File getTempVideoFile(AbstractFile file) { + try { + return getVideoFileInTempDir(file); + } catch (NoCurrentCaseException ex) { + // Mimic the old behavior. + throw new IllegalStateException(ex); + } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 1404ca4950..3f1ca56e47 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -257,18 +257,18 @@ public abstract class AbstractAbstractFileNode extends A * Used by subclasses of AbstractAbstractFileNode to add the tags property * to their sheets. * - * @param ss the modifiable Sheet.Set returned by + * @param sheetSet the modifiable Sheet.Set returned by * Sheet.get(Sheet.PROPERTIES) */ @NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags") - protected void addTagProperty(Sheet.Set ss) { + protected void addTagProperty(Sheet.Set sheetSet) { List tags = new ArrayList<>(); try { - tags.addAll(Case.getOpenCase().getServices().getTagsManager().getContentTagsByContent(content)); + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content)); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex); } - ss.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(), + sheetSet.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(), NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()) .distinct() .collect(Collectors.joining(", ")))); @@ -296,7 +296,7 @@ public abstract class AbstractAbstractFileNode extends A } } - private static String getHashSetHitsForFile(AbstractFile file) { + public static String getHashSetHitsForFile(AbstractFile file) { try { return StringUtils.join(file.getHashSetNames(), ", "); } catch (TskCoreException tskCoreException) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 8750d57f30..b4c34e5da7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -120,7 +120,7 @@ public abstract class AbstractContentNode extends ContentNode + " AND type = " + TskData.ObjectType.ABSTRACTFILE.getObjectType() + ") AS OBJECT_IDS"; //NON-NLS; - try (SleuthkitCase.CaseDbQuery dbQuery = Case.getOpenCase().getSleuthkitCase().executeQuery(query)) { + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); if(resultSet.next()){ return (0 < resultSet.getInt("count")); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java index 372ae3e6ea..b0e33c13bf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java @@ -63,11 +63,11 @@ public abstract class AbstractFsContentNode extends Abst @Override @NbBundle.Messages("AbstractFsContentNode.noDesc.text=no description") protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } Map map = new LinkedHashMap<>(); @@ -76,16 +76,16 @@ public abstract class AbstractFsContentNode extends Abst final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) { final String propString = propType.toString(); - ss.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); + sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); } if (directoryBrowseMode) { - ss.put(new NodeProperty<>(HIDE_PARENT, HIDE_PARENT, HIDE_PARENT, HIDE_PARENT)); + sheetSet.put(new NodeProperty<>(HIDE_PARENT, HIDE_PARENT, HIDE_PARENT, HIDE_PARENT)); } // add tags property to the sheet - addTagProperty(ss); + addTagProperty(sheetSet); - return s; + return sheet; } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java index 6aaa05af94..4092321ca9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java @@ -32,6 +32,6 @@ public interface AutopsyVisitableItem { * * @return visitor return value */ - public T accept(AutopsyItemVisitor v); + public T accept(AutopsyItemVisitor visitor); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index e042c196a4..ab64cead5e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -266,7 +266,7 @@ public class BlackboardArtifactNode extends AbstractContentNode map = new LinkedHashMap<>(); fillPropertyMap(map, artifact); - ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), NO_DESCR, this.getSourceName())); @@ -332,12 +332,12 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), + BlackboardArtifact associatedArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.displayName"), NO_DESCR, associatedArtifact.getDisplayName() + " " + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.artifact.displayName"))); - ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.displayName"), NO_DESCR, associatedArtifact.getShortDescription())); @@ -348,7 +348,7 @@ public class BlackboardArtifactNode extends AbstractContentNode entry : map.entrySet()) { - ss.put(new NodeProperty<>(entry.getKey(), + sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); @@ -357,7 +357,7 @@ public class BlackboardArtifactNode extends AbstractContentNode np : customProperties) { - ss.put(np); + sheetSet.put(np); } } @@ -375,11 +375,11 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"), NO_DESCR, ext)); - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.displayName"), NO_DESCR, @@ -395,7 +395,7 @@ public class BlackboardArtifactNode extends AbstractContentNode( + sheetSet.put(new NodeProperty<>( NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.displayName"), NO_DESCR, @@ -404,27 +404,27 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getMtime(), file))); - ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getCtime(), file))); - ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getAtime(), file))); - ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getCrtime(), file))); - ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), "", associated.getSize())); - ss.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), Bundle.BlackboardArtifactNode_createSheet_artifactMD5_displayName(), "", file == null ? "" : StringUtils.defaultString(file.getMd5Hash()))); @@ -443,7 +443,7 @@ public class BlackboardArtifactNode extends AbstractContentNode( + sheetSet.put(new NodeProperty<>( NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.displayName"), NO_DESCR, @@ -451,30 +451,30 @@ public class BlackboardArtifactNode extends AbstractContentNode tags = new ArrayList<>(); try { - tags.addAll(Case.getOpenCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); - tags.addAll(Case.getOpenCase().getServices().getTagsManager().getContentTagsByContent(associated)); + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(associated)); } catch (TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Failed to get tags for artifact " + artifact.getDisplayName(), ex); } - ss.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), + sheetSet.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); } @@ -591,8 +591,8 @@ public class BlackboardArtifactNode extends AbstractContentNode T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } /** @@ -630,7 +630,7 @@ public class BlackboardArtifactNode extends AbstractContentNode T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java index 6e96b9689c..6e7a8205c7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -138,8 +138,8 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CommonFileChildNodeLoading.java b/Core/src/org/sleuthkit/autopsy/datamodel/CommonFileChildNodeLoading.java new file mode 100644 index 0000000000..0c83a12f61 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/CommonFileChildNodeLoading.java @@ -0,0 +1,95 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; + +/** + * A dummy node used by the child factory to display while children are being + * generated + */ +public class CommonFileChildNodeLoading extends DisplayableItemNode { + + public CommonFileChildNodeLoading(Children children) { + super(children); + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + Map map = new LinkedHashMap<>(); + map.put(CommonFileChildLoadingPropertyType.File.toString(), "Loading..."); + + final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); + for (CommonFileChildLoadingPropertyType propType : CommonFileChildLoadingPropertyType.values()) { + final String propString = propType.toString(); + sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); + } + + return sheet; + } + + /** + * Represents the sole column for the 'dummy' loading node. + */ + @NbBundle.Messages({ + "CommonFileChildLoadingPropertyType.fileColLbl=File" + }) + public enum CommonFileChildLoadingPropertyType { + + File(Bundle.CommonFileChildLoadingPropertyType_fileColLbl()); + + final private String displayString; + + private CommonFileChildLoadingPropertyType(String displayString) { + this.displayString = displayString; + } + + @Override + public String toString() { + return displayString; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java index 0dfd0efb01..d3531068ee 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java @@ -44,5 +44,5 @@ abstract class ContentNode extends DisplayableItemNode { * * @return visitor's visit return value */ - public abstract T accept(ContentNodeVisitor v); + public abstract T accept(ContentNodeVisitor visitor); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 745753f2d4..59d7d21015 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -134,8 +134,8 @@ class ContentTagNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index e7aa3d8fe4..5f8cbfff54 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -27,7 +27,7 @@ public class DataSources implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 0833b87b43..6e8374252d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -103,7 +103,7 @@ public class DataSourcesNode extends DisplayableItemNode { private void reloadKeys() { try { - currentKeys = Case.getOpenCase().getDataSources(); + currentKeys = Case.getCurrentCaseThrows().getDataSources(); setKeys(currentKeys); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS @@ -127,23 +127,23 @@ public class DataSourcesNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.desc"), NAME)); - return s; + return sheet; } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 816105d080..812eaa065b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -95,8 +95,8 @@ public class DeletedContent implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } } @@ -105,8 +105,8 @@ public class DeletedContent implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } public SleuthkitCase getSleuthkitCase() { @@ -131,8 +131,8 @@ public class DeletedContent implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -140,18 +140,18 @@ public class DeletedContent implements AutopsyVisitableItem { "DeletedContent.createSheet.name.displayName=Name", "DeletedContent.createSheet.name.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>("Name", //NON-NLS + sheetSet.put(new NodeProperty<>("Name", //NON-NLS Bundle.DeletedContent_createSheet_name_displayName(), Bundle.DeletedContent_createSheet_name_desc(), NAME)); - return s; + return sheet; } @Override @@ -207,7 +207,7 @@ public class DeletedContent implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); // new file was added // @@@ COULD CHECK If the new file is deleted before notifying... update(); @@ -226,7 +226,7 @@ public class DeletedContent implements AutopsyVisitableItem { * received for a case that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); update(); } catch (NoCurrentCaseException notUsed) { /** @@ -305,8 +305,8 @@ public class DeletedContent implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -314,19 +314,19 @@ public class DeletedContent implements AutopsyVisitableItem { "DeletedContent.createSheet.filterType.displayName=Type", "DeletedContent.createSheet.filterType.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>("Type", //NON_NLS + sheetSet.put(new NodeProperty<>("Type", //NON_NLS Bundle.DeletedContent_createSheet_filterType_displayName(), Bundle.DeletedContent_createSheet_filterType_desc(), filter.getDisplayName())); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 9dd1296ae1..97b0fd8216 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -104,13 +104,13 @@ public class DirectoryNode extends AbstractFsContentNode { } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 2713992139..6d90aa9b0e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.commonfilesearch.CommonFilesNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; @@ -109,6 +110,14 @@ public interface DisplayableItemNodeVisitor { T visit(InterestingHits.SetNameNode ihsn); + T visit(Md5Node mn); + + T visit(CommonFilesNode cfn); + + T visit(FileInstanceNode fin); + + T visit(CommonFileChildNodeLoading cfcnl); + /* * Tags */ @@ -177,6 +186,26 @@ public interface DisplayableItemNodeVisitor { */ protected abstract T defaultVisit(DisplayableItemNode c); + @Override + public T visit(FileInstanceNode fin) { + return defaultVisit(fin); + } + + @Override + public T visit(Md5Node mn) { + return defaultVisit(mn); + } + + @Override + public T visit(CommonFilesNode cfn) { + return defaultVisit(cfn); + } + + @Override + public T visit(CommonFileChildNodeLoading cfcnl) { + return defaultVisit(cfcnl); + } + @Override public T visit(DirectoryNode dn) { return defaultVisit(dn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 935aec9c62..409c3165e8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -95,8 +95,8 @@ public class EmailExtracted implements AutopsyVisitableItem { @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } private final class EmailResults extends Observable { @@ -193,25 +193,25 @@ public class EmailExtracted implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override @@ -241,7 +241,7 @@ public class EmailExtracted implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -266,7 +266,7 @@ public class EmailExtracted implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); emailResults.update(); } catch (NoCurrentCaseException notUsed) { /** @@ -339,19 +339,19 @@ public class EmailExtracted implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override @@ -360,8 +360,8 @@ public class EmailExtracted implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -435,24 +435,24 @@ public class EmailExtracted implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmptyNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmptyNode.java index c822743c23..332dc2807a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmptyNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmptyNode.java @@ -85,8 +85,8 @@ public final class EmptyNode extends AbstractNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 3e001b097d..304073c9b6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -64,8 +64,8 @@ public class ExtractedContent implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } public SleuthkitCase getSleuthkitCase() { @@ -152,24 +152,24 @@ public class ExtractedContent implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.desc"), NAME)); - return s; + return sheet; } @Override @@ -211,7 +211,7 @@ public class ExtractedContent implements AutopsyVisitableItem { * may be received for a case that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Due to some unresolved issues with how cases are closed, * it is possible for the event to have a null oldValue if @@ -234,7 +234,7 @@ public class ExtractedContent implements AutopsyVisitableItem { * may be received for a case that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { /** @@ -342,29 +342,29 @@ public class ExtractedContent implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.name"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.displayName"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.desc"), type.getDisplayName())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.name"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.displayName"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.desc"), childCount)); - return s; + return sheet; } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -402,7 +402,7 @@ public class ExtractedContent implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -427,7 +427,7 @@ public class ExtractedContent implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java new file mode 100644 index 0000000000..dea5535a26 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java @@ -0,0 +1,127 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Used by the Common Files search feature to encapsulate instances of a given + * MD5s matched in the search. These nodes will be children of Md5Nodes. + */ +public class FileInstanceNode extends FileNode { + + private final String dataSource; + + public FileInstanceNode(AbstractFile fsContent, String dataSource) { + super(fsContent); + this.content = fsContent; + this.dataSource = dataSource; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public AbstractFile getContent() { + return this.content; + } + + String getDataSource() { + return this.dataSource; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + Map map = new LinkedHashMap<>(); + fillPropertyMap(map, this); + + final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); + for (CommonFilePropertyType propType : CommonFilePropertyType.values()) { + final String propString = propType.toString(); + final Object property = map.get(propString); + final NodeProperty nodeProperty = new NodeProperty<>(propString, propString, NO_DESCR, property); + sheetSet.put(nodeProperty); + } + + this.addTagProperty(sheetSet); + + return sheet; + } + + /** + * Fill map with AbstractFile properties + * + * @param map map with preserved ordering, where property names/values are + * put + * @param node The item to get properties for. + */ + static private void fillPropertyMap(Map map, FileInstanceNode node) { + + map.put(CommonFilePropertyType.File.toString(), node.getName()); + map.put(CommonFilePropertyType.ParentPath.toString(), node.getContent().getParentPath()); + map.put(CommonFilePropertyType.HashsetHits.toString(), getHashSetHitsForFile(node.getContent())); + map.put(CommonFilePropertyType.DataSource.toString(), node.getDataSource()); + map.put(CommonFilePropertyType.MimeType.toString(), StringUtils.defaultString(node.content.getMIMEType())); + } + + /** + * Encapsulates the columns to be displayed for reach row represented by an + * instance of this object. + */ + @NbBundle.Messages({ + "CommonFilePropertyType.fileColLbl=File", + "CommonFilePropertyType.pathColLbl=Parent Path", + "CommonFilePropertyType.hashsetHitsColLbl=Hash Set Hits", + "CommonFilePropertyType.dataSourceColLbl=Data Source", + "CommonFilePropertyType.mimeTypeColLbl=MIME Type" + }) + public enum CommonFilePropertyType { + + File(Bundle.CommonFilePropertyType_fileColLbl()), + ParentPath(Bundle.CommonFilePropertyType_pathColLbl()), + HashsetHits(Bundle.CommonFilePropertyType_hashsetHitsColLbl()), + DataSource(Bundle.CommonFilePropertyType_dataSourceColLbl()), + MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl()); + + final private String displayString; + + private CommonFilePropertyType(String displayString) { + this.displayString = displayString; + } + + @Override + public String toString() { + return displayString; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 0a9321e4d5..4bf994fb0c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -91,8 +91,8 @@ public class FileSize implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } } @@ -101,8 +101,8 @@ public class FileSize implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } public SleuthkitCase getSleuthkitCase() { @@ -129,24 +129,24 @@ public class FileSize implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.desc"), NAME)); - return s; + return sheet; } @Override @@ -202,7 +202,7 @@ public class FileSize implements AutopsyVisitableItem { try { // new file was added // @@@ could check the size here and only fire off updates if we know the file meets the min size criteria - Case.getOpenCase(); + Case.getCurrentCaseThrows(); update(); } catch (NoCurrentCaseException notUsed) { /** @@ -219,7 +219,7 @@ public class FileSize implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); update(); } catch (NoCurrentCaseException notUsed) { /** @@ -314,25 +314,25 @@ public class FileSize implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.name"), NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.displayName"), NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.desc"), filter.getDisplayName())); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java index 53822532bb..e4dca77d01 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -78,8 +78,8 @@ public final class FileTypes implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } SleuthkitCase getSleuthkitCase() { @@ -127,8 +127,8 @@ public final class FileTypes implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -137,19 +137,19 @@ public final class FileTypes implements AutopsyVisitableItem { "FileTypes.createSheet.name.displayName=Name", "FileTypes.createSheet.name.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(Bundle.FileTypes_createSheet_name_name(), + sheetSet.put(new NodeProperty<>(Bundle.FileTypes_createSheet_name_name(), Bundle.FileTypes_createSheet_name_displayName(), Bundle.FileTypes_createSheet_name_desc(), NAME )); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 2d8cc564d2..2d04fe9733 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -65,8 +65,8 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } /** @@ -94,7 +94,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); typesRoot.updateShowCounts(); update(); } catch (NoCurrentCaseException notUsed) { @@ -177,17 +177,17 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } if (filter != null && (filter.equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER) || filter.equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER))) { String extensions = ""; @@ -195,11 +195,11 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { extensions += "'" + ext + "', "; } extensions = extensions.substring(0, extensions.lastIndexOf(',')); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), extensions)); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), extensions)); } else { - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.desc"), getDisplayName())); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.desc"), getDisplayName())); } - return s; + return sheet; } @Override @@ -297,28 +297,28 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.desc"), filter.getDisplayName())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), String.join(", ", filter.getFilter()))); - return s; + return sheet; } @Override @@ -463,8 +463,8 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } @Override @@ -520,8 +520,8 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } @Override @@ -567,8 +567,8 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index e7af90fab7..e41477d96e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -164,7 +164,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); typesRoot.updateShowCounts(); populateHashMap(); } catch (NoCurrentCaseException notUsed) { @@ -184,8 +184,8 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } /** @@ -231,8 +231,8 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -304,20 +304,20 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.name"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.desc"), getDisplayName())); - return s; + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.name"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.desc"), getDisplayName())); + return sheet; } @Override @@ -395,20 +395,20 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } @Override - public T accept(DisplayableItemNodeVisitor< T> v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor< T> visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.name"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.desc"), getDisplayName())); - return s; + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.name"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.desc"), getDisplayName())); + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index a1b9cf9963..a720409489 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -70,8 +70,8 @@ public class HashsetHits implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } /** @@ -160,25 +160,25 @@ public class HashsetHits implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override @@ -209,7 +209,7 @@ public class HashsetHits implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Due to some unresolved issues with how cases are * closed, it is possible for the event to have a null @@ -233,7 +233,7 @@ public class HashsetHits implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); hashsetResults.update(); } catch (NoCurrentCaseException notUsed) { /** @@ -314,24 +314,24 @@ public class HashsetHits implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 7c06fb7afb..d11d7c64b2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -135,46 +135,46 @@ public class ImageNode extends AbstractContentNode { "ImageNode.createSheet.deviceId.displayName=Device ID", "ImageNode.createSheet.deviceId.desc=Device ID of the image"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.desc"), getDisplayName())); - ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_type_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_type_name(), Bundle.ImageNode_createSheet_type_displayName(), Bundle.ImageNode_createSheet_type_desc(), Bundle.ImageNode_createSheet_type_text())); - ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_size_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_size_name(), Bundle.ImageNode_createSheet_size_displayName(), Bundle.ImageNode_createSheet_size_desc(), this.content.getSize())); - ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_sectorSize_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_sectorSize_name(), Bundle.ImageNode_createSheet_sectorSize_displayName(), Bundle.ImageNode_createSheet_sectorSize_desc(), this.content.getSsize())); - ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_md5_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_md5_name(), Bundle.ImageNode_createSheet_md5_displayName(), Bundle.ImageNode_createSheet_md5_desc(), this.content.getMd5())); - ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_timezone_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_timezone_name(), Bundle.ImageNode_createSheet_timezone_displayName(), Bundle.ImageNode_createSheet_timezone_desc(), this.content.getTimeZone())); - try (CaseDbQuery query = Case.getOpenCase().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { + try (CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { ResultSet deviceIdSet = query.getResultSet(); if (deviceIdSet.next()) { - ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_deviceId_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_deviceId_name(), Bundle.ImageNode_createSheet_deviceId_displayName(), Bundle.ImageNode_createSheet_deviceId_desc(), deviceIdSet.getString("device_id"))); @@ -183,12 +183,12 @@ public class ImageNode extends AbstractContentNode { logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex); } - return s; + return sheet; } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -197,8 +197,8 @@ public class ImageNode extends AbstractContentNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 435fa75023..7c3249f990 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -134,8 +134,8 @@ public class InterestingHits implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } /** @@ -156,25 +156,25 @@ public class InterestingHits implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override @@ -200,7 +200,7 @@ public class InterestingHits implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -226,7 +226,7 @@ public class InterestingHits implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); interestingResults.update(); } catch (NoCurrentCaseException notUsed) { /** @@ -302,24 +302,24 @@ public class InterestingHits implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -392,22 +392,22 @@ public class InterestingHits implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), getName())); - return s; + return sheet; } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.java index 0331bbbb1b..2904a1aecb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.java @@ -73,16 +73,16 @@ public class KeyValueNode extends AbstractNode { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } // table view drops first column of properties under assumption // that it contains the node's name - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeyValueNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeyValueNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "KeyValueNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "KeyValueNode.createSheet.name.desc"), data.getName())); @@ -90,13 +90,13 @@ public class KeyValueNode extends AbstractNode { for (Map.Entry entry : data.getMap().entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); - ss.put(new NodeProperty<>(key, + sheetSet.put(new NodeProperty<>(key, key, NbBundle.getMessage(this.getClass(), "KeyValueNode.createSheet.map.desc"), value)); } - return s; + return sheet; } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index dbe3dc371b..c35d9bdda0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -326,8 +326,8 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } // Created by CreateAutopsyNodeVisitor @@ -346,8 +346,8 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -355,20 +355,20 @@ public class KeywordHits implements AutopsyVisitableItem { "KeywordHits.createSheet.name.displayName=Name", "KeywordHits.createSheet.name.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_name_name(), KeywordHits_createSheet_name_displayName(), KeywordHits_createSheet_name_desc(), getName())); - return s; + return sheet; } @Override @@ -412,7 +412,7 @@ public class KeywordHits implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -435,7 +435,7 @@ public class KeywordHits implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); keywordResults.update(); } catch (NoCurrentCaseException notUsed) { // Case is closed, do nothing. @@ -547,26 +547,26 @@ public class KeywordHits implements AutopsyVisitableItem { "KeywordHits.createSheet.numChildren.displayName=Number of Children", "KeywordHits.createSheet.numChildren.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_listName_name(), KeywordHits_createSheet_listName_displayName(), KeywordHits_createSheet_listName_desc(), listName)); - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_numChildren_name(), KeywordHits_createSheet_numChildren_displayName(), KeywordHits_createSheet_numChildren_desc(), keywordResults.getKeywords(listName).size())); - return s; + return sheet; } @Override @@ -575,8 +575,8 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } } @@ -636,8 +636,8 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -645,25 +645,25 @@ public class KeywordHits implements AutopsyVisitableItem { "KeywordHits.createSheet.filesWithHits.displayName=Files with Hits", "KeywordHits.createSheet.filesWithHits.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_listName_name(), KeywordHits_createSheet_listName_displayName(), KeywordHits_createSheet_listName_desc(), getDisplayName())); - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_filesWithHits_name(), KeywordHits_createSheet_filesWithHits_displayName(), KeywordHits_createSheet_filesWithHits_desc(), countTotalDescendants())); - return s; + return sheet; } } @@ -777,32 +777,32 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_listName_name(), KeywordHits_createSheet_listName_displayName(), KeywordHits_createSheet_listName_desc(), getDisplayName())); - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( KeywordHits_createSheet_filesWithHits_name(), KeywordHits_createSheet_filesWithHits_displayName(), KeywordHits_createSheet_filesWithHits_desc(), keywordResults.getArtifactIds(setName, keyword, instance).size())); - return s; + return sheet; } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 0d541d7e1a..20d4601e9d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -71,35 +71,35 @@ public class LayoutFileNode extends AbstractAbstractFileNode { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } Map map = new LinkedHashMap<>(); fillPropertyMap(map); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.desc"), getName())); final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.noDescr.text"); for (Map.Entry entry : map.entrySet()) { - ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } // add tags property to the sheet - addTagProperty(ss); + addTagProperty(sheetSet); - return s; + return sheet; } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -108,8 +108,8 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java index f9bab8d5b2..053146daea 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java @@ -48,14 +48,14 @@ public class LocalDirectoryNode extends SpecialDirectoryNode { "LocalDirectoryNode.createSheet.name.desc=no description", "LocalDirectoryNode.createSheet.noDesc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(Bundle.LocalDirectoryNode_createSheet_name_name(), + sheetSet.put(new NodeProperty<>(Bundle.LocalDirectoryNode_createSheet_name_name(), Bundle.LocalDirectoryNode_createSheet_name_displayName(), Bundle.LocalDirectoryNode_createSheet_name_desc(), getName())); @@ -67,20 +67,20 @@ public class LocalDirectoryNode extends SpecialDirectoryNode { final String NO_DESCR = Bundle.LocalDirectoryNode_createSheet_noDesc(); for (Map.Entry entry : map.entrySet()) { - ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } - addTagProperty(ss); + addTagProperty(sheetSet); - return s; + return sheet; } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index d743f1a0d4..c723506363 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -67,30 +67,30 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } Map map = new LinkedHashMap<>(); fillPropertyMap(map, getContent()); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.desc"), getName())); final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.noDescr.text"); for (Map.Entry entry : map.entrySet()) { - ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } // add tags property to the sheet - addTagProperty(ss); + addTagProperty(sheetSet); - return s; + return sheet; } @Override @@ -132,13 +132,13 @@ public class LocalFileNode extends AbstractAbstractFileNode { } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java new file mode 100644 index 0000000000..96de7eba36 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java @@ -0,0 +1,183 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.commonfilesearch.FileInstanceMetadata; +import org.sleuthkit.autopsy.commonfilesearch.Md5Metadata; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Represents a common files match - two or more files which appear to be the + * same file and appear as children of this node. This node will simply contain + * the MD5 of the matched files, the data sources those files were found within, + * and a count of the instances represented by the md5. + */ +public class Md5Node extends DisplayableItemNode { + + private static final Logger LOGGER = Logger.getLogger(Md5Node.class.getName()); + + private final String md5Hash; + private final int commonFileCount; + private final String dataSources; + + public Md5Node(Md5Metadata data) { + super(Children.create( + new FileInstanceNodeFactory(data), true), + Lookups.singleton(data.getMd5())); + + this.commonFileCount = data.size(); + this.dataSources = String.join(", ", data.getDataSources()); + this.md5Hash = data.getMd5(); + + this.setDisplayName(this.md5Hash); + } + + int getCommonFileCount() { + return this.commonFileCount; + } + + String getDataSources() { + return this.dataSources; + } + + public String getMd5() { + return this.md5Hash; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + Map map = new LinkedHashMap<>(); + fillPropertyMap(map, this); + + final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); + for (Md5Node.CommonFileParentPropertyType propType : Md5Node.CommonFileParentPropertyType.values()) { + final String propString = propType.toString(); + sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); + } + + return sheet; + } + + /** + * Fill map with AbstractFile properties + * + * @param map map with preserved ordering, where property names/values are + * put + * @param node The item to get properties for. + */ + static private void fillPropertyMap(Map map, Md5Node node) { + map.put(CommonFileParentPropertyType.File.toString(), node.getMd5()); + map.put(CommonFileParentPropertyType.InstanceCount.toString(), node.getCommonFileCount()); + map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /** + * Child generator for FileInstanceNode of Md5Node. + */ + static class FileInstanceNodeFactory extends ChildFactory { + + private final Md5Metadata descendants; + + FileInstanceNodeFactory(Md5Metadata descendants) { + this.descendants = descendants; + } + + @Override + protected Node createNodeForKey(FileInstanceMetadata file) { + try { + Case currentCase = Case.getCurrentCaseThrows(); + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + AbstractFile abstractFile = tskDb.findAllFilesWhere(String.format("obj_id in (%s)", file.getObjectId())).get(0); + + return new FileInstanceNode(abstractFile, file.getDataSourceName()); + } catch (NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to create node for file with obj_id: %s.", new Object[]{file.getObjectId()}), ex); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to create node for file with obj_id: %s.", new Object[]{file.getObjectId()}), ex); + } + return null; + } + + @Override + protected boolean createKeys(List list) { + list.addAll(this.descendants.getMetadata()); + return true; + } + } + + @NbBundle.Messages({ + "CommonFileParentPropertyType.fileColLbl=File", + "CommonFileParentPropertyType.instanceColLbl=Instance Count", + "CommonFileParentPropertyType.dataSourceColLbl=Data Source"}) + public enum CommonFileParentPropertyType { + + File(Bundle.CommonFileParentPropertyType_fileColLbl()), + InstanceCount(Bundle.CommonFileParentPropertyType_instanceColLbl()), + DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()); + + final private String displayString; + + private CommonFileParentPropertyType(String displayString) { + this.displayString = displayString; + } + + @Override + public String toString() { + return displayString; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java index ab39b69890..be0dc8dbcd 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java @@ -77,8 +77,8 @@ public class RecentFiles implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } } @@ -88,8 +88,8 @@ public class RecentFiles implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } public SleuthkitCase getSleuthkitCase() { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java index 66cbc5fb68..86045f5e4b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java @@ -56,26 +56,26 @@ public class RecentFilesFilterNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>( + sheetSet.put(new NodeProperty<>( NbBundle.getMessage(this.getClass(), "RecentFilesFilterNode.createSheet.filterType.name"), NbBundle.getMessage(this.getClass(), "RecentFilesFilterNode.createSheet.filterType.displayName"), NbBundle.getMessage(this.getClass(), "RecentFilesFilterNode.createSheet.filterType.desc"), filter.getDisplayName())); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java index f404b3ac75..d4daf0784d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java @@ -30,13 +30,11 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class RecentFilesNode extends DisplayableItemNode { private static final String NAME = NbBundle.getMessage(RecentFilesNode.class, "RecentFilesNode.name.text"); - private SleuthkitCase skCase; - + RecentFilesNode(SleuthkitCase skCase) { super(Children.create(new RecentFilesChildren(skCase), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); - this.skCase = skCase; this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/recent_files.png"); //NON-NLS } @@ -46,24 +44,24 @@ public class RecentFilesNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.desc"), NAME)); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java index bda7a9cf08..b2f7667150 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java @@ -115,7 +115,7 @@ public final class Reports implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); ReportNodeFactory.this.refresh(true); } catch (NoCurrentCaseException notUsed) { /** @@ -129,7 +129,7 @@ public final class Reports implements AutopsyVisitableItem { @Override protected boolean createKeys(List keys) { try { - keys.addAll(Case.getOpenCase().getAllReports()); + keys.addAll(Case.getCurrentCaseThrows().getAllReports()); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(Reports.ReportNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get reports", ex); //NON-NLS } @@ -266,7 +266,7 @@ public final class Reports implements AutopsyVisitableItem { NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.title"), JOptionPane.YES_NO_OPTION)) { try { - Case.getOpenCase().deleteReports(selectedReportsCollection); + Case.getCurrentCaseThrows().deleteReports(selectedReportsCollection); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(DeleteReportAction.class.getName()).log(Level.SEVERE, "Error deleting reports", ex); // NON-NLS MessageNotifyUtil.Message.error(Bundle.DeleteReportAction_showConfirmDialog_errorMsg()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 61e9ad626e..7f1cd1cea7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -32,8 +32,8 @@ public class Results implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } public SleuthkitCase getSleuthkitCase() { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 2363e2cf27..87a3d5f903 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -55,8 +55,8 @@ public class ResultsNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -65,19 +65,19 @@ public class ResultsNode extends DisplayableItemNode { "ResultsNode.createSheet.name.displayName=Name", "ResultsNode.createSheet.name.desc=no description"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(Bundle.ResultsNode_createSheet_name_name(), + sheetSet.put(new NodeProperty<>(Bundle.ResultsNode_createSheet_name_name(), Bundle.ResultsNode_createSheet_name_displayName(), Bundle.ResultsNode_createSheet_name_desc(), NAME )); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java index 60d3912a84..ba377b1e0e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java @@ -99,13 +99,13 @@ public class SlackFileNode extends AbstractFsContentNode { } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } // Given a file, returns the correct icon for said diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 57fd35beba..9a01fa608f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -58,8 +58,8 @@ public class Tags implements AutopsyVisitableItem { private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } /** @@ -96,8 +96,8 @@ public class Tags implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -142,7 +142,7 @@ public class Tags implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); tagResults.update(); } catch (NoCurrentCaseException notUsed) { @@ -159,7 +159,7 @@ public class Tags implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); tagResults.update(); } catch (NoCurrentCaseException notUsed) { @@ -196,7 +196,7 @@ public class Tags implements AutopsyVisitableItem { @Override protected boolean createKeys(List keys) { try { - List tagNamesInUse = Case.getOpenCase().getServices().getTagsManager().getTagNamesInUse(); + List tagNamesInUse = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); Collections.sort(tagNamesInUse); keys.addAll(tagNamesInUse); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -243,7 +243,7 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - TagsManager tm = Case.getOpenCase().getServices().getTagsManager(); + TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagsCount = tm.getContentTagsCountByTagName(tagName); tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -266,10 +266,10 @@ public class Tags implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { + public T accept(DisplayableItemNodeVisitor visitor) { // See classes derived from DisplayableItemNodeVisitor // for behavior added using the Visitor pattern. - return v.visit(this); + return visitor.visit(this); } @Override @@ -348,7 +348,7 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getOpenCase().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -369,8 +369,8 @@ public class Tags implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -403,7 +403,7 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { // Use the content tags bearing the specified tag name as the keys. try { - keys.addAll(Case.getOpenCase().getServices().getTagsManager().getContentTagsByTagName(tagName)); + keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName)); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -447,7 +447,7 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getOpenCase().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -468,8 +468,8 @@ public class Tags implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -502,7 +502,7 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { try { // Use the blackboard artifact tags bearing the specified tag name as the keys. - keys.addAll(Case.getOpenCase().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); + keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index 4a9dee9886..b31cfda543 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -32,8 +32,8 @@ public class Views implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } public SleuthkitCase getSleuthkitCase() { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 9d1f0e0d63..08f2369a61 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -53,24 +53,24 @@ public class ViewsNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.desc"), NAME)); - return s; + return sheet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 312e6a8da0..9a7e3eae8e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -75,14 +75,14 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode { "VirtualDirectoryNode.createSheet.deviceId.displayName=Device ID", "VirtualDirectoryNode.createSheet.deviceId.desc=Device ID of the image"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.desc"), @@ -94,22 +94,22 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode { final String NO_DESCR = NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.noDesc"); for (Map.Entry entry : map.entrySet()) { - ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } - addTagProperty(ss); + addTagProperty(sheetSet); } else { - ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_type_name(), + sheetSet.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_type_name(), Bundle.VirtualDirectoryNode_createSheet_type_displayName(), Bundle.VirtualDirectoryNode_createSheet_type_desc(), Bundle.VirtualDirectoryNode_createSheet_type_text())); - ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_size_name(), + sheetSet.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_size_name(), Bundle.VirtualDirectoryNode_createSheet_size_displayName(), Bundle.VirtualDirectoryNode_createSheet_size_desc(), this.content.getSize())); - try (SleuthkitCase.CaseDbQuery query = Case.getOpenCase().getSleuthkitCase().executeQuery("SELECT time_zone FROM data_source_info WHERE obj_id = " + this.content.getId())) { + try (SleuthkitCase.CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery("SELECT time_zone FROM data_source_info WHERE obj_id = " + this.content.getId())) { ResultSet timeZoneSet = query.getResultSet(); if (timeZoneSet.next()) { - ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_timezone_name(), + sheetSet.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_timezone_name(), Bundle.VirtualDirectoryNode_createSheet_timezone_displayName(), Bundle.VirtualDirectoryNode_createSheet_timezone_desc(), timeZoneSet.getString("time_zone"))); @@ -117,10 +117,10 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode { } catch (SQLException | TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Failed to get time zone for the following image: " + this.content.getId(), ex); } - try (SleuthkitCase.CaseDbQuery query = Case.getOpenCase().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { + try (SleuthkitCase.CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { ResultSet deviceIdSet = query.getResultSet(); if (deviceIdSet.next()) { - ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_deviceId_name(), + sheetSet.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_deviceId_name(), Bundle.VirtualDirectoryNode_createSheet_deviceId_displayName(), Bundle.VirtualDirectoryNode_createSheet_deviceId_desc(), deviceIdSet.getString("device_id"))); @@ -131,16 +131,16 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode { } - return s; + return sheet; } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index 62230a5422..9612441910 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -150,44 +150,44 @@ public class VolumeNode extends AbstractContentNode { @Override protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.desc"), this.getDisplayName())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.name"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.displayName"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.desc"), content.getAddr())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.name"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.displayName"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.desc"), content.getStart())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.name"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.displayName"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.desc"), content.getLength())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.name"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.displayName"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.desc"), content.getDescription())); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.name"), + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.name"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.displayName"), NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.desc"), content.getFlagsAsString())); - return s; + return sheet; } @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -196,8 +196,8 @@ public class VolumeNode extends AbstractContentNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 72d2999a98..8301928528 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -115,8 +115,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); } /** @@ -207,8 +207,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -238,7 +238,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -262,7 +262,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { // Case is closed, do nothing. @@ -364,7 +364,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -388,7 +388,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { @@ -408,6 +408,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -415,6 +416,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -481,8 +483,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -513,7 +515,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -537,7 +539,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { @@ -569,6 +571,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -576,6 +579,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -623,8 +627,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -647,7 +651,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -671,7 +675,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { @@ -691,6 +695,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -698,6 +703,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -824,8 +830,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -858,7 +864,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before @@ -882,7 +888,7 @@ final public class Accounts implements AutopsyVisitableItem { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); refresh(true); } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause @@ -901,6 +907,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -908,6 +915,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -1018,8 +1026,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -1207,8 +1215,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -1386,8 +1394,8 @@ final public class Accounts implements AutopsyVisitableItem { } @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); } @Override @@ -1395,13 +1403,13 @@ final public class Accounts implements AutopsyVisitableItem { return getClass().getName(); } - private Sheet.Set getPropertySet(Sheet s) { - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); + private Sheet.Set getPropertySet(Sheet sheet) { + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); } - return ss; + return sheetSet; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java index a65e32c8a6..f8ff559afd 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java @@ -127,7 +127,7 @@ final class AddRawImageTask implements Runnable { private void addImageToCase(List dataSources, List errorMessages) { SleuthkitCase caseDatabase; try { - caseDatabase = Case.getOpenCase().getSleuthkitCase(); + caseDatabase = Case.getCurrentCaseThrows().getSleuthkitCase(); } catch (NoCurrentCaseException ex) { errorMessages.add(Bundle.AddRawImageTask_noOpenCase_errMsg()); logger.log(Level.SEVERE, Bundle.AddRawImageTask_noOpenCase_errMsg(), ex); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java index a8572a4664..baf9b15223 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java @@ -301,7 +301,7 @@ final class RawDSInputPanel extends JPanel implements DocumentListener { "RawDSInputPanel.noOpenCase.errMsg=Exception while getting open case."}) private void warnIfPathIsInvalid(String path) { try { - if (!PathValidator.isValid(path, Case.getOpenCase().getCaseType())) { + if (!PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.RawDSInputPanel_error_text()); } diff --git a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java index 1525958390..d50f31e383 100644 --- a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java +++ b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java @@ -299,7 +299,7 @@ public class PerformancePanel extends javax.swing.JDialog { Case curCase; try { - curCase = Case.getOpenCase(); + curCase = Case.getCurrentCaseThrows(); } catch (Exception e) { setImgLabel(NbBundle.getMessage(this.getClass(), "PerformancePanel.label.caseNotOpen.text")); setStatusMsg(""); @@ -380,7 +380,7 @@ public class PerformancePanel extends javax.swing.JDialog { Case curCase; try { - curCase = Case.getOpenCase(); + curCase = Case.getCurrentCaseThrows(); } catch (Exception e) { setFileReadLabel( NbBundle.getMessage(this.getClass(), "PerformancePanel.label.caseNotOpen.text")); @@ -472,7 +472,7 @@ public class PerformancePanel extends javax.swing.JDialog { Case curCase; try { - curCase = Case.getOpenCase(); + curCase = Case.getCurrentCaseThrows(); } catch (Exception e) { setDbLabel(NbBundle.getMessage(this.getClass(), "PerformancePanel.label.caseNotOpen.text")); return; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index f51c94aaca..3aef1e5a2a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -386,8 +386,15 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); } // action to go to the source file of the artifact - actionsList.add(new ViewContextAction( + // action to go to the source file of the artifact + Content fileContent = ban.getLookup().lookup(AbstractFile.class); + if (fileContent == null) { + Content content = ban.getLookup().lookup(Content.class); + actionsList.add(new ViewContextAction("View Source Content in Directory", content)); + } else { + actionsList.add(new ViewContextAction( NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban)); + } } Content c = ban.getLookup().lookup(File.class); Node n = null; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 13219f863d..0fd19c98a1 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final transient ExplorerManager em = new ExplorerManager(); private static DirectoryTreeTopComponent instance; - private final DataResultTopComponent dataResult = new DataResultTopComponent(true, Bundle.DirectoryTreeTopComponent_resultsView_title()); + private final DataResultTopComponent dataResult = new DataResultTopComponent(Bundle.DirectoryTreeTopComponent_resultsView_title()); private final LinkedList backList; private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS @@ -364,7 +364,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); Case currentCase = null; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { // No open case. } @@ -526,7 +526,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * sources. */ try { - Case openCase = Case.getOpenCase(); + Case openCase = Case.getCurrentCaseThrows(); return openCase.hasData() == false; } catch (NoCurrentCaseException ex) { return true; @@ -619,7 +619,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * already closed. */ try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); // We only need to trigger openCoreWindows() when the // first data source is added. if (currentCase.getDataSources().size() == 1) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java index 991b554c63..3451b073a3 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java @@ -89,7 +89,7 @@ public class ExternalViewerAction extends AbstractAction { // Get the temp folder path of the case Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 28a8bac071..2f45e44331 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -101,7 +101,7 @@ public final class ExtractAction extends AbstractAction { private void extractFile(ActionEvent e, AbstractFile selectedFile) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS @@ -127,7 +127,7 @@ public final class ExtractAction extends AbstractAction { private void extractFiles(ActionEvent e, Collection selectedFiles) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index 893bd94da4..b0729d24c6 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -121,7 +121,7 @@ final class ExtractUnallocAction extends AbstractAction { } Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { MessageNotifyUtil.Message.info(Bundle.ExtractAction_noOpenCase_errMsg()); return; @@ -611,7 +611,7 @@ final class ExtractUnallocAction extends AbstractAction { this.imageId = img.getId(); this.imageName = img.getName(); this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + 0 + ".dat"; //NON-NLS - this.fileInstance = new File(Case.getOpenCase().getExportDirectory() + File.separator + this.fileName); + this.fileInstance = new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.fileName); this.sizeInBytes = calcSizeInBytes(); } @@ -633,7 +633,7 @@ final class ExtractUnallocAction extends AbstractAction { this.imageId = 0; } this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + volumeId + ".dat"; //NON-NLS - this.fileInstance = new File(Case.getOpenCase().getExportDirectory() + File.separator + this.fileName); + this.fileInstance = new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.fileName); this.layoutFiles = getUnallocFiles(volume); Collections.sort(layoutFiles, new SortObjId()); this.sizeInBytes = calcSizeInBytes(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewSourceArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewSourceArtifactAction.java index 494908285b..bc7c10b3c3 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewSourceArtifactAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewSourceArtifactAction.java @@ -49,7 +49,7 @@ class ViewSourceArtifactAction extends AbstractAction { try { for (BlackboardAttribute attribute : artifact.getAttributes()) { if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) { - BlackboardArtifact associatedArtifact = Case.getOpenCase().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); + BlackboardArtifact associatedArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); if (associatedArtifact != null) { dirTree.viewArtifact(associatedArtifact); break; diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/examples/SampleDataSourceIngestModule.java index 8605d33c13..9a1bd96cc7 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleDataSourceIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleDataSourceIngestModule.java @@ -77,7 +77,7 @@ class SampleDataSourceIngestModule implements DataSourceIngestModule { try { // Get count of files with .doc extension. - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); List docFiles = fileManager.findFiles(dataSource, "%.doc"); long fileCount = 0; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java index 661e08a542..048ccd5f79 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java @@ -141,7 +141,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { try { // get the latest case - Case currentCase = Case.getOpenCase(); // get the most updated case + Case currentCase = Case.getCurrentCaseThrows(); // get the most updated case Set caseTimeZones = currentCase.getTimeZones(); Iterator iterator = caseTimeZones.iterator(); @@ -282,7 +282,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { * that is already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); SwingUtilities.invokeLater(DateSearchFilter.this::updateTimeZoneList); } catch (NoCurrentCaseException notUsed) { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java index 32ebb9005a..9171104238 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java @@ -86,3 +86,4 @@ class FileSearchDialog extends javax.swing.JDialog { private org.sleuthkit.autopsy.filesearch.FileSearchPanel fileSearchPanel1; // End of variables declaration//GEN-END:variables } + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index ae894f632a..169b36d7ea 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -153,7 +153,7 @@ class FileSearchPanel extends javax.swing.JPanel { String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); // try to get the number of matches first - Case currentCase = Case.getOpenCase(); // get the most updated case + Case currentCase = Case.getCurrentCaseThrows(); // get the most updated case long totalMatches = 0; List contentList = null; try { diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SearchChildren.java b/Core/src/org/sleuthkit/autopsy/filesearch/SearchChildren.java index cfa4631a29..9e31177826 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SearchChildren.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SearchChildren.java @@ -53,5 +53,4 @@ class SearchChildren extends Children.Keys { return node; } } - } diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java index 6aab063952..9ab4ec281b 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.guiutils; -import java.awt.Color; import java.awt.Component; import java.time.Duration; import javax.swing.JTable; @@ -41,36 +40,47 @@ public class DurationCellRenderer extends GrayableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value instanceof Long) { { - Duration d = Duration.ofMillis((long) value); - if (d.isNegative()) { - d = Duration.ofMillis(-(long) value); - } - - String result; - long days = d.toDays(); - long hours = d.minusDays(days).toHours(); - long minutes = d.minusDays(days).minusHours(hours).toMinutes(); - long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds(); - - if (minutes > 0) { - if (hours > 0) { - if (days > 0) { - result = days + " d " + hours + " h " + minutes + " m " + seconds + " s"; - } else { - result = hours + " h " + minutes + " m " + seconds + " s"; - } - } else { - result = minutes + " m " + seconds + " s"; - } - } else { - result = seconds + " s"; - } - - setText(result); + setText(DurationCellRenderer.longToDurationString((long) value)); } } grayCellIfTableNotEnabled(table, isSelected); return this; } + /** + * Convert a duration represented by a long to a human readable string with + * with days, hours, minutes, and seconds components. + * + * @param duration - the representation of the duration in long form + * + * @return - the representation of the duration in String form. + */ + public static String longToDurationString(long duration) { + Duration d = Duration.ofMillis(duration); + if (d.isNegative()) { + d = Duration.ofMillis(-duration); + } + + String result; + long days = d.toDays(); + long hours = d.minusDays(days).toHours(); + long minutes = d.minusDays(days).minusHours(hours).toMinutes(); + long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds(); + + if (minutes > 0) { + if (hours > 0) { + if (days > 0) { + result = days + " d " + hours + " h " + minutes + " m " + seconds + " s"; + } else { + result = hours + " h " + minutes + " m " + seconds + " s"; + } + } else { + result = minutes + " m " + seconds + " s"; + } + } else { + result = seconds + " s"; + } + return result; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java new file mode 100644 index 0000000000..2c455c1743 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -0,0 +1,859 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.List; +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import org.apache.commons.dbcp2.BasicDataSource; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.ThreadUtils; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Class for recording data on the health of the system. + * + * For timing data: + * Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object + * Modules will call submitTimingMetric() with the obtained TimingMetric object to log it + */ +public final class EnterpriseHealthMonitor implements PropertyChangeListener { + + private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName()); + private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; + private final static String MODULE_NAME = "EnterpriseHealthMonitor"; + private final static String IS_ENABLED_KEY = "is_enabled"; + private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes + public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION + = new CaseDbSchemaVersionNumber(1, 0); + + private static final AtomicBoolean isEnabled = new AtomicBoolean(false); + private static EnterpriseHealthMonitor instance; + + private final ExecutorService healthMonitorExecutor; + private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; + + private ScheduledThreadPoolExecutor healthMonitorOutputTimer; + private final Map timingInfoMap; + private static final int CONN_POOL_SIZE = 10; + private BasicDataSource connectionPool = null; + private String hostName; + + private EnterpriseHealthMonitor() throws HealthMonitorException { + + // Create the map to collect timing metrics. The map will exist regardless + // of whether the monitor is enabled. + timingInfoMap = new HashMap<>(); + + // Set up the executor to handle case events + healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); + + // Get the host name + try { + hostName = java.net.InetAddress.getLocalHost().getHostName(); + } catch (java.net.UnknownHostException ex) { + // Continue on, but log the error and generate a UUID to use for this session + hostName = UUID.randomUUID().toString(); + logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex); + } + + // Read from module settings to determine if the module is enabled + if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { + if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ + isEnabled.set(true); + try { + activateMonitor(); + } catch (HealthMonitorException ex) { + // If we failed to activate it, then disable the monitor + logger.log(Level.SEVERE, "Health monitor activation failed - disabling health monitor"); + setEnabled(false); + throw ex; + } + return; + } + } + isEnabled.set(false); + } + + /** + * Get the instance of the EnterpriseHealthMonitor + * @return the instance + * @throws HealthMonitorException + */ + synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException { + if (instance == null) { + instance = new EnterpriseHealthMonitor(); + Case.addPropertyChangeListener(instance); + } + return instance; + } + + /** + * Activate the health monitor. + * Creates/initialized the database (if needed), clears any existing metrics + * out of the maps, and sets up the timer for writing to the database. + * @throws HealthMonitorException + */ + private synchronized void activateMonitor() throws HealthMonitorException { + + logger.log(Level.INFO, "Activating Servies Health Monitor"); + + if (!UserPreferences.getIsMultiUserModeEnabled()) { + throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor"); + } + + // Set up database (if needed) + try (CoordinationService.Lock lock = getExclusiveDbLock()) { + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + // Check if the database exists + if (! databaseExists()) { + + // If not, create a new one + createDatabase(); + } + + if( ! databaseIsInitialized()) { + initializeDatabaseSchema(); + } + + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + + // Clear out any old data + timingInfoMap.clear(); + + // Start the timer for database writes + startTimer(); + } + + /** + * Deactivate the health monitor. + * This should only be used when disabling the monitor, not when Autopsy is closing. + * Clears out any metrics that haven't been written, stops the database write timer, + * and shuts down the connection pool. + * @throws HealthMonitorException + */ + private synchronized void deactivateMonitor() throws HealthMonitorException { + + logger.log(Level.INFO, "Deactivating Servies Health Monitor"); + + // Clear out the collected data + timingInfoMap.clear(); + + // Stop the timer + stopTimer(); + + // Shut down the connection pool + shutdownConnections(); + } + + /** + * Start the ScheduledThreadPoolExecutor that will handle the database writes. + */ + private synchronized void startTimer() { + // Make sure the previous executor (if it exists) has been stopped + stopTimer(); + + healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); + healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); + } + + /** + * Stop the ScheduledThreadPoolExecutor to prevent further database writes. + */ + private synchronized void stopTimer() { + if(healthMonitorOutputTimer != null) { + ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer); + } + } + + /** + * Called from the installer to set up the Health Monitor instance at startup. + * @throws HealthMonitorException + */ + static synchronized void startUpIfEnabled() throws HealthMonitorException { + getInstance(); + } + + /** + * Enabled/disable the health monitor. + * @param enabled true to enable the monitor, false to disable it + * @throws HealthMonitorException + */ + static synchronized void setEnabled(boolean enabled) throws HealthMonitorException { + if(enabled == isEnabled.get()) { + // The setting has not changed, so do nothing + return; + } + + if(enabled) { + getInstance().activateMonitor(); + + // If activateMonitor fails, we won't update either of these + ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true"); + isEnabled.set(true); + } else { + ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false"); + isEnabled.set(false); + getInstance().deactivateMonitor(); + } + } + + /** + * Get a metric that will measure the time to execute a section of code. + * Call this before the section of code to be timed and then + * submit it afterward using submitTimingMetric(). + * This method is safe to call regardless of whether the Enterprise Health + * Monitor is enabled. + * @param name A short but descriptive name describing the code being timed. + * This name will appear in the UI. + * @return The TimingMetric object + */ + public static TimingMetric getTimingMetric(String name) { + if(isEnabled.get()) { + return new TimingMetric(name); + } + return null; + } + + /** + * Submit the metric that was previously obtained through getTimingMetric(). + * Call this immediately after the section of code being timed. + * This method is safe to call regardless of whether the Enterprise Health + * Monitor is enabled. + * @param metric The TimingMetric object obtained from getTimingMetric() + */ + public static void submitTimingMetric(TimingMetric metric) { + if(isEnabled.get() && (metric != null)) { + metric.stopTiming(); + try { + getInstance().addTimingMetric(metric); + } catch (HealthMonitorException ex) { + // We don't want calling methods to have to check for exceptions, so just log it + logger.log(Level.SEVERE, "Error adding timing metric", ex); + } + } + } + + /** + * Submit the metric that was previously obtained through getTimingMetric(), + * incorporating a count that the time should be divided by. + * Call this immediately after the section of code being timed. + * This method is safe to call regardless of whether the Enterprise Health + * Monitor is enabled. + * @param metric The TimingMetric object obtained from getTimingMetric() + * @param normalization The number to divide the time by (a zero here will be treated as a one) + */ + public static void submitNormalizedTimingMetric(TimingMetric metric, long normalization) { + if(isEnabled.get() && (metric != null)) { + metric.stopTiming(); + try { + metric.normalize(normalization); + getInstance().addTimingMetric(metric); + } catch (HealthMonitorException ex) { + // We don't want calling methods to have to check for exceptions, so just log it + logger.log(Level.SEVERE, "Error adding timing metric", ex); + } + } + } + + /** + * Add the timing metric data to the map. + * @param metric The metric to add. stopTiming() should already have been called. + */ + private void addTimingMetric(TimingMetric metric) throws HealthMonitorException { + + // Do as little as possible within the synchronized block to minimize + // blocking with multiple threads. + synchronized(this) { + // There's a small check-then-act situation here where isEnabled + // may have changed before reaching this code. This is fine - + // the map still exists and any extra data added after the monitor + // is disabled will be deleted if the monitor is re-enabled. This + // seems preferable to doing another check on isEnabled within + // the synchronized block. + if(timingInfoMap.containsKey(metric.getName())) { + timingInfoMap.get(metric.getName()).addMetric(metric); + } else { + timingInfoMap.put(metric.getName(), new TimingInfo(metric)); + } + } + } + + /** + * Time a database query. + * Database queries are hard to test in normal processing because the time + * is so dependent on the size of the tables being queried. We use getImages here + * because every table it queries is the same size (one entry for each image) so + * we a) know the size of the tables and b) can use that table size to do + * normalization. + * @throws HealthMonitorException + */ + private void performDatabaseQuery() throws HealthMonitorException { + try { + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query"); + List images = skCase.getImages(); + + // Through testing we found that this normalization gives us fairly + // consistent results for different numbers of data sources. + long normalization = images.size(); + if (images.isEmpty()) { + normalization += 2; + } else if (images.size() == 1){ + normalization += 3; + } else if (images.size() < 10) { + normalization += 5; + } else { + normalization += 7; + } + + EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization); + } catch (NoCurrentCaseException ex) { + // If there's no case open, we just can't do the metrics. + } catch (TskCoreException ex) { + throw new HealthMonitorException("Error running getImages()", ex); + } + } + + /** + * Collect metrics at a scheduled time. + * @throws HealthMonitorException + */ + private void gatherTimerBasedMetrics() throws HealthMonitorException { + // Time a database query + performDatabaseQuery(); + } + + /** + * Write the collected metrics to the database. + * @throws HealthMonitorException + */ + private void writeCurrentStateToDatabase() throws HealthMonitorException { + + Map timingMapCopy; + + // Do as little as possible within the synchronized block since it will + // block threads attempting to record metrics. + synchronized(this) { + if(! isEnabled.get()) { + return; + } + + // Make a shallow copy of the timing map. The map should be small - one entry + // per metric name. + timingMapCopy = new HashMap<>(timingInfoMap); + timingInfoMap.clear(); + } + + // Check if there's anything to report (right now we only have the timing map) + if(timingMapCopy.keySet().isEmpty()) { + return; + } + + logger.log(Level.INFO, "Writing health monitor metrics to database"); + + // Write to the database + try (CoordinationService.Lock lock = getSharedDbLock()) { + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + // Add timing metrics to the database + String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { + + for(String name:timingMapCopy.keySet()) { + TimingInfo info = timingMapCopy.get(name); + + statement.setString(1, name); + statement.setString(2, hostName); + statement.setLong(3, System.currentTimeMillis()); + statement.setLong(4, info.getCount()); + statement.setDouble(5, info.getAverage()); + statement.setDouble(6, info.getMax()); + statement.setDouble(7, info.getMin()); + + statement.execute(); + } + + } catch (SQLException ex) { + throw new HealthMonitorException("Error saving metric data to database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } + + /** + * Check whether the health monitor database exists. + * Does not check the schema. + * @return true if the database exists, false otherwise + * @throws HealthMonitorException + */ + private boolean databaseExists() throws HealthMonitorException { + try { + // Use the same database settings as the case + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + ResultSet rs = null; + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; + rs = statement.executeQuery(createCommand); + if(rs.next()) { + logger.log(Level.INFO, "Existing Enterprise Health Monitor database found"); + return true; + } + } finally { + if(rs != null) { + rs.close(); + } + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + throw new HealthMonitorException("Failed check for health monitor database", ex); + } + return false; + } + + /** + * Create a new health monitor database. + * @throws HealthMonitorException + */ + private void createDatabase() throws HealthMonitorException { + try { + // Use the same database settings as the case + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String createCommand = "CREATE DATABASE \"" + DATABASE_NAME + "\" OWNER \"" + db.getUserName() + "\""; //NON-NLS + statement.execute(createCommand); + } + logger.log(Level.INFO, "Created new health monitor database " + DATABASE_NAME); + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + throw new HealthMonitorException("Failed to delete health monitor database", ex); + } + } + + /** + * Setup a connection pool for db connections. + * @throws HealthMonitorException + */ + private void setupConnectionPool() throws HealthMonitorException { + try { + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + + connectionPool = new BasicDataSource(); + connectionPool.setDriverClassName("org.postgresql.Driver"); + + StringBuilder connectionURL = new StringBuilder(); + connectionURL.append("jdbc:postgresql://"); + connectionURL.append(db.getHost()); + connectionURL.append(":"); + connectionURL.append(db.getPort()); + connectionURL.append("/"); + connectionURL.append(DATABASE_NAME); + + connectionPool.setUrl(connectionURL.toString()); + connectionPool.setUsername(db.getUserName()); + connectionPool.setPassword(db.getPassword()); + + // tweak pool configuration + connectionPool.setInitialSize(3); // start with 3 connections + connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections + connectionPool.setValidationQuery("SELECT version()"); + } catch (UserPreferencesException ex) { + throw new HealthMonitorException("Error loading database configuration", ex); + } + } + + /** + * Shut down the connection pool + * @throws HealthMonitorException + */ + private void shutdownConnections() throws HealthMonitorException { + try { + synchronized(this) { + if(connectionPool != null){ + connectionPool.close(); + connectionPool = null; // force it to be re-created on next connect() + } + } + } catch (SQLException ex) { + throw new HealthMonitorException("Failed to close existing database connections.", ex); // NON-NLS + } + } + + /** + * Get a database connection. + * Sets up the connection pool if needed. + * @return The Connection object + * @throws HealthMonitorException + */ + private Connection connect() throws HealthMonitorException { + synchronized (this) { + if (connectionPool == null) { + setupConnectionPool(); + } + } + + try { + return connectionPool.getConnection(); + } catch (SQLException ex) { + throw new HealthMonitorException("Error getting connection from connection pool.", ex); // NON-NLS + } + } + + /** + * Test whether the database schema has been initialized. + * We do this by looking for the version number. + * @return True if it has been initialized, false otherwise. + * @throws HealthMonitorException + */ + private boolean databaseIsInitialized() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + ResultSet resultSet = null; + + try (Statement statement = conn.createStatement()) { + resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); + return resultSet.next(); + } catch (SQLException ex) { + // This likely just means that the db_info table does not exist + return false; + } finally { + if(resultSet != null) { + try { + resultSet.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing result set", ex); + } + } + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + + /** + * Get the current schema version + * @return the current schema version + * @throws HealthMonitorException + */ + private CaseDbSchemaVersionNumber getVersion() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + ResultSet resultSet = null; + + try (Statement statement = conn.createStatement()) { + int minorVersion = 0; + int majorVersion = 0; + resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'"); + if (resultSet.next()) { + String minorVersionStr = resultSet.getString("value"); + try { + minorVersion = Integer.parseInt(minorVersionStr); + } catch (NumberFormatException ex) { + throw new HealthMonitorException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt"); + } + } + + resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); + if (resultSet.next()) { + String majorVersionStr = resultSet.getString("value"); + try { + majorVersion = Integer.parseInt(majorVersionStr); + } catch (NumberFormatException ex) { + throw new HealthMonitorException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt"); + } + } + + return new CaseDbSchemaVersionNumber(majorVersion, minorVersion); + } catch (SQLException ex) { + throw new HealthMonitorException("Error initializing database", ex); + } finally { + if(resultSet != null) { + try { + resultSet.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing result set", ex); + } + } + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + + /** + * Initialize the database. + * @throws HealthMonitorException + */ + private void initializeDatabaseSchema() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + try (Statement statement = conn.createStatement()) { + conn.setAutoCommit(false); + + String createTimingTable = + "CREATE TABLE IF NOT EXISTS timing_data (" + + "id SERIAL PRIMARY KEY," + + "name text NOT NULL," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "count bigint NOT NULL," + + "average double precision NOT NULL," + + "max double precision NOT NULL," + + "min double precision NOT NULL" + + ")"; + statement.execute(createTimingTable); + + String createDbInfoTable = + "CREATE TABLE IF NOT EXISTS db_info (" + + "id SERIAL PRIMARY KEY NOT NULL," + + "name text NOT NULL," + + "value text NOT NULL" + + ")"; + statement.execute(createDbInfoTable); + + statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); + statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')"); + + conn.commit(); + } catch (SQLException ex) { + try { + conn.rollback(); + } catch (SQLException ex2) { + logger.log(Level.SEVERE, "Rollback error"); + } + throw new HealthMonitorException("Error initializing database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing connection.", ex); + } + } + } + + /** + * The task called by the ScheduledThreadPoolExecutor to handle + * the database writes. + */ + static final class DatabaseWriteTask implements Runnable { + + /** + * Write current metric data to the database + */ + @Override + public void run() { + try { + getInstance().gatherTimerBasedMetrics(); + getInstance().writeCurrentStateToDatabase(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS + } + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + + switch (Case.Events.valueOf(evt.getPropertyName())) { + + case CURRENT_CASE: + if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { + // When a case is closed, write the current metrics to the database + healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask()); + } + break; + } + } + + /** + * Get an exclusive lock for the health monitor database. + * Acquire this before creating, initializing, or updating the database schema. + * @return The lock + * @throws HealthMonitorException + */ + private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ + try { + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES); + + if(lock != null){ + return lock; + } + throw new HealthMonitorException("Error acquiring database lock"); + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + throw new HealthMonitorException("Error acquiring database lock", ex); + } + } + + /** + * Get an shared lock for the health monitor database. + * Acquire this before database reads or writes. + * @return The lock + * @throws HealthMonitorException + */ + private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{ + try { + String databaseNodeName = DATABASE_NAME; + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); + + if(lock != null){ + return lock; + } + throw new HealthMonitorException("Error acquiring database lock"); + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + throw new HealthMonitorException("Error acquiring database lock"); + } + } + + /** + * Internal class for collecting timing metrics. + * Instead of storing each TimingMetric, we only store the min and max + * seen and the number of metrics and total duration to compute the average + * later. + * One TimingInfo instance should be created per metric name, and + * additional timing metrics will be added to it. + */ + private class TimingInfo { + private long count; // Number of metrics collected + private double sum; // Sum of the durations collected (nanoseconds) + private double max; // Maximum value found (nanoseconds) + private double min; // Minimum value found (nanoseconds) + + TimingInfo(TimingMetric metric) throws HealthMonitorException { + count = 1; + sum = metric.getDuration(); + max = metric.getDuration(); + min = metric.getDuration(); + } + + /** + * Add a new TimingMetric to an existing TimingInfo object. + * This is called in a synchronized block for almost all new + * TimingMetric objects, so do as little processing here as possible. + * @param metric The new metric + * @throws HealthMonitorException Will be thrown if the metric hasn't been stopped + */ + void addMetric(TimingMetric metric) throws HealthMonitorException { + + // Keep track of needed info to calculate the average + count++; + sum += metric.getDuration(); + + // Check if this is the longest duration seen + if(max < metric.getDuration()) { + max = metric.getDuration(); + } + + // Check if this is the lowest duration seen + if(min > metric.getDuration()) { + min = metric.getDuration(); + } + } + + /** + * Get the average duration + * @return average duration (milliseconds) + */ + double getAverage() { + return sum / count; + } + + /** + * Get the maximum duration + * @return maximum duration (milliseconds) + */ + double getMax() { + return max; + } + + /** + * Get the minimum duration + * @return minimum duration (milliseconds) + */ + double getMin() { + return min; + } + + /** + * Get the total number of metrics collected + * @return number of metrics collected + */ + long getCount() { + return count; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java new file mode 100644 index 0000000000..6df918acaa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java @@ -0,0 +1,34 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.healthmonitor; + +/** + * Exception used internally by the Services Health Monitor + */ +class HealthMonitorException extends Exception { + private static final long serialVersionUID = 1L; + + HealthMonitorException(String message) { + super(message); + } + + HealthMonitorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java new file mode 100644 index 0000000000..61ea5a5244 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -0,0 +1,53 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import java.util.logging.Level; +import org.openide.modules.ModuleInstall; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; + +public class Installer extends ModuleInstall { + + private static final Logger logger = Logger.getLogger(Installer.class.getName()); + private static final long serialVersionUID = 1L; + + private static Installer instance; + + public synchronized static Installer getDefault() { + if (instance == null) { + instance = new Installer(); + } + return instance; + } + + private Installer() { + super(); + } + + @Override + public void restored() { + + try { + EnterpriseHealthMonitor.startUpIfEnabled(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error starting health services monitor", ex); + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java new file mode 100644 index 0000000000..d44ceca910 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -0,0 +1,84 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.healthmonitor; + +/** + * Used to calculate and report timing metrics. + */ +public class TimingMetric { + + private final String name; + private final long startingTimestamp; + private Double duration; + + TimingMetric(String name) { + this.name = name; + this.startingTimestamp = System.nanoTime(); + this.duration = null; + } + + /** + * Record how long the metric was running. + */ + void stopTiming() { + long endingTimestamp = System.nanoTime(); + this.duration = (double)(endingTimestamp - startingTimestamp) / 1000000; + } + + /** + * Get the name of metric + * @return name + */ + String getName() { + return name; + } + + /** + * Get the duration of the metric. Will throw an exception if the + * metric has not been stopped. + * @return how long the metric was running (milliseconds) + * @throws HealthMonitorException + */ + double getDuration() throws HealthMonitorException { + if (duration != null) { + return duration; + } else { + throw new HealthMonitorException("getDuration() called before stopTiming()"); + } + } + + /** + * Normalize the metric by dividing the time by the given counter. + * If the counter is zero, it will be treated the same way as if the + * counter were one. + * @param count Value to divide the duration by + * @throws HealthMonitorException + */ + void normalize(long count) throws HealthMonitorException { + if (duration != null) { + if(count < 0) { + throw new HealthMonitorException("normalize() called with negative count (" + count + ")"); + } else if(count > 1) { + duration = duration / count; + } // If count is 0 or 1, do nothing + } else { + throw new HealthMonitorException("normalize() called before stopTiming()"); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java index b7d064aa44..71404c76e7 100644 --- a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java @@ -83,7 +83,7 @@ class ImageWriter implements PropertyChangeListener{ // null before Image Writer finishes. The user can still elect to wait for image writer // (in ImageWriterService.closeCaseResources) even though the case is closing. try{ - caseDb = Case.getOpenCase().getSleuthkitCase(); + caseDb = Case.getCurrentCaseThrows().getSleuthkitCase(); } catch (NoCurrentCaseException ex){ logger.log(Level.SEVERE, "Unable to load case. Image writer will be cancelled."); this.isCancelled = true; @@ -152,7 +152,7 @@ class ImageWriter implements PropertyChangeListener{ Image image; try{ - image = Case.getOpenCase().getSleuthkitCase().getImageById(dataSourceId); + image = Case.getCurrentCaseThrows().getSleuthkitCase().getImageById(dataSourceId); imageHandle = image.getImageHandle(); } catch (NoCurrentCaseException ex){ // This exception means that getOpenCase() failed because no case was open. diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index fe9535916e..c59b3f20bf 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -277,7 +277,7 @@ final class DataSourceIngestJob { Thread.currentThread().interrupt(); } try { - SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); this.addIngestModules(firstStageDataSourceModuleTemplates, IngestModuleType.DATA_SOURCE_LEVEL, skCase); this.addIngestModules(fileIngestModuleTemplates, IngestModuleType.FILE_LEVEL, skCase); this.addIngestModules(secondStageDataSourceModuleTemplates, IngestModuleType.DATA_SOURCE_LEVEL, skCase); @@ -415,7 +415,7 @@ final class DataSourceIngestJob { List errors = startUpIngestPipelines(); if (errors.isEmpty()) { try { - this.ingestJob = Case.getOpenCase().getSleuthkitCase().addIngestJob(dataSource, NetworkUtils.getLocalHostName(), ingestModules, new Date(this.createTime), new Date(0), IngestJobStatusType.STARTED, ""); + this.ingestJob = Case.getCurrentCaseThrows().getSleuthkitCase().addIngestJob(dataSource, NetworkUtils.getLocalHostName(), ingestModules, new Date(this.createTime), new Date(0), IngestJobStatusType.STARTED, ""); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Failed to add ingest job to database.", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GetFilesCountVisitor.java b/Core/src/org/sleuthkit/autopsy/ingest/GetFilesCountVisitor.java index 94b9b2a93b..8796009f2a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/GetFilesCountVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/GetFilesCountVisitor.java @@ -47,7 +47,7 @@ final class GetFilesCountVisitor extends ContentVisitor.Default { //case of a real fs, query all files for it SleuthkitCase sc; try { - sc = Case.getOpenCase().getSleuthkitCase(); + sc = Case.getCurrentCaseThrows().getSleuthkitCase(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return 0L; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java index 3a06a3be4d..d512c6670c 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java @@ -501,13 +501,16 @@ public final class IngestJobSettings { this.warnings.add(warning); } } else { - try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { - settings = (IngestModuleIngestJobSettings) in.readObject(); - } catch (IOException | ClassNotFoundException exception) { - String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS - logger.log(Level.WARNING, warning, exception); - this.warnings.add(warning); - } + // @@@ BC Jython serialization is currently broken and this + // throws an exception. (-2323). Commenting out so that + // Python modules will at least load with default settings. +// try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { +// settings = (IngestModuleIngestJobSettings) in.readObject(); +// } catch (IOException | ClassNotFoundException exception) { +// String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS +// logger.log(Level.WARNING, warning, exception); +// this.warnings.add(warning); +// } } } if (settings == null) { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java index d03616d6f0..88fb0b347e 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java @@ -98,7 +98,7 @@ public final class IngestJobSettingsPanel extends javax.swing.JPanel { this.settings = settings; this.dataSources.addAll(dataSources); try { - SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); ingestJobs.addAll(skCase.getIngestJobs()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "No open case", ex); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 6511a6693e..28a7c44474 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -191,7 +191,7 @@ public class IngestManager { * only necessary for multi-user cases. */ try { - if (Case.getOpenCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { + if (Case.getCurrentCaseThrows().getCaseType() != Case.CaseType.MULTI_USER_CASE) { return; } } catch (NoCurrentCaseException noCaseOpenException) { @@ -252,7 +252,7 @@ public class IngestManager { caseIsOpen = true; clearIngestMessageBox(); try { - Case openedCase = Case.getOpenCase(); + Case openedCase = Case.getCurrentCaseThrows(); String channelPrefix = openedCase.getName(); if (Case.CaseType.MULTI_USER_CASE == openedCase.getCaseType()) { jobEventPublisher.openRemoteEventChannel(String.format(INGEST_JOB_EVENT_CHANNEL_NAME, channelPrefix)); @@ -369,7 +369,7 @@ public class IngestManager { List errors = null; Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return new IngestJobStartResult(null, new IngestManagerException("Exception while getting open case.", ex), Collections.emptyList()); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java index 52698415d3..17645ccb68 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java @@ -248,7 +248,7 @@ class IngestMessageDetailsPanel extends javax.swing.JPanel { long objId = artifact.getObjectID(); AbstractFile file = null; try { - file = Case.getOpenCase().getSleuthkitCase().getAbstractFileById(objId); + file = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(objId); } catch (TskException | NoCurrentCaseException ex) { } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java index 764fc694ef..071c6bb489 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java @@ -150,11 +150,11 @@ public final class IngestMonitor { */ private void findRootDirectoryForCurrentCase() { try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); findRootDirectoryForCurrentCase(currentCase); } catch (NoCurrentCaseException unused) { /* - * Case.getOpenCase() throws NoCurrentCaseException when there + * Case.getCurrentOpenCase() throws NoCurrentCaseException when there * is no case. */ root = new File(File.separator); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java index f70c9b0813..01e86823c5 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2014 Basis Technology Corp. + * + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,20 +26,25 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.datamodel.SleuthkitCase; /** - * Singleton class that provides services for ingest modules. These exist to - * make it easier to write modules. Use the getDefault() method to get the - * singleton instance. + * Ingest services provides convenience methods for ingest modules to use during + * to access the Autopsy case, the case database, fire events, etc. */ public final class IngestServices { private static IngestServices instance = null; - private final IngestManager manager = IngestManager.getInstance(); + /** + * Constructs an ingest services object that provides convenience methods + * for ingest modules to use to access the Autopsy case, the case database, + * fire events, etc. + */ private IngestServices() { } /** - * Get the ingest services. + * Gets the ingest services singleton that provides convenience methods for + * ingest modules to use to access the Autopsy case, the case database, fire + * events, * * @return The ingest services singleton. */ @@ -51,123 +56,150 @@ public final class IngestServices { } /** - * Get the current Autopsy case. + * Gets the current open Autopsy case. + * + * @return The current open case. * - * @return The current case. * @throws NoCurrentCaseException if there is no open case. */ - public Case getOpenCase() throws NoCurrentCaseException { - return Case.getOpenCase(); + public Case getCase() throws NoCurrentCaseException { + return Case.getCurrentCaseThrows(); } /** - * Get the current SleuthKit case. The SleuthKit case is the case database. + * Gets the case database of the current open Autopsy case. * * @return The current case database. + * * @throws NoCurrentCaseException if there is no open case. */ - public SleuthkitCase getCurrentSleuthkitCaseDb() throws NoCurrentCaseException { - return Case.getOpenCase().getSleuthkitCase(); + public SleuthkitCase getCaseDatabase() throws NoCurrentCaseException { + return Case.getCurrentCaseThrows().getSleuthkitCase(); } /** - * Get a logger that incorporates the display name of an ingest module in + * Gets a logger that incorporates the display name of an ingest module in * messages written to the Autopsy log files. * * @param moduleDisplayName The display name of the ingest module. * - * @return The custom logger for the ingest module. + * @return A logger for the ingest module. */ public Logger getLogger(String moduleDisplayName) { return Logger.getLogger(moduleDisplayName); } /** - * Post message to the ingest messages in box. + * Posts a message to the ingest messages in box. * * @param message An ingest message */ public void postMessage(final IngestMessage message) { - manager.postIngestMessage(message); + IngestManager.getInstance().postIngestMessage(message); } /** - * Fire module data event to notify registered module data event listeners - * that there is new data of a given type from a module. + * Fires an event to notify registered listeners that a new artifact has + * been posted to the blackboard. * - * @param moduleDataEvent module data event, encapsulating blackboard - * artifact data + * @param moduleDataEvent A module data event, i.e., an event that + * encapsulates artifact data. */ public void fireModuleDataEvent(ModuleDataEvent moduleDataEvent) { IngestManager.getInstance().fireIngestModuleDataEvent(moduleDataEvent); } /** - * Fire module content event to notify registered module content event - * listeners that there is new content (from ZIP file contents, carving, - * etc.) + * Fires an event to notify registered listeners that there is new content + * (e.g., files extracted from an archive file, carved files, etc.) * - * @param moduleContentEvent module content event, encapsulating content - * changed + * @param moduleContentEvent A module content event, i.e., an event that + * encapsulates new content data. */ public void fireModuleContentEvent(ModuleContentEvent moduleContentEvent) { IngestManager.getInstance().fireIngestModuleContentEvent(moduleContentEvent); } /** - * Get free disk space of a drive where ingest data are written to That - * drive is being monitored by IngestMonitor thread when ingest is running. + * Gets the free disk space of the drive where data is written during + * ingest. Can be used by ingest modules to determine if there is enough + * disk space before writing data is attmepted. * - * @return amount of disk space, -1 if unknown + * @return Amount of free disk space, in bytes, or -1 if unknown. */ public long getFreeDiskSpace() { - return manager.getFreeDiskSpace(); + return IngestManager.getInstance().getFreeDiskSpace(); } /** - * Gets a specific name/value configuration setting for a module + * Gets a global configuration setting for an ingest module. * - * @param moduleName moduleName identifier unique to that module - * @param settingName setting name to retrieve + * @param moduleName A unique identifier for the module. + * @param settingName The name of the setting. * - * @return setting value for the module / setting name, or null if not found + * @return setting The value of the setting, or null if not found. */ public String getConfigSetting(String moduleName, String settingName) { return ModuleSettings.getConfigSetting(moduleName, settingName); } /** - * Sets a specific name/value configuration setting for a module + * Sets a global configuration setting for an ingest module. * - * @param moduleName moduleName identifier unique to that module - * @param settingName setting name to set - * @param settingVal setting value to set + * @param moduleName A unique identifier for the module. + * @param settingName The name of the setting. + * @param setting The value of the setting. */ - public void setConfigSetting(String moduleName, String settingName, String settingVal) { - ModuleSettings.setConfigSetting(moduleName, settingName, settingVal); + public void setConfigSetting(String moduleName, String settingName, String setting) { + ModuleSettings.setConfigSetting(moduleName, settingName, setting); } /** - * Gets all name/value configuration settings for a module + * Gets all of the global configuration settings for an ingest module. * - * @param moduleName moduleName identifier unique to that module + * @param moduleName A unique identifier for the module. * - * @return settings for the module / setting name + * @return A mapping of setting names to setting values. */ public Map getConfigSettings(String moduleName) { return ModuleSettings.getConfigSettings(moduleName); } /** - * Sets all name/value configuration setting for a module. Names not in the - * list will have settings preserved. + * Sets all of the global configuration settings for an ingest module. + * + * @param moduleName A unique identifier for the module. * * @param moduleName moduleName identifier unique to that module - * @param settings settings to set and replace old settings, keeping - * settings not specified in the map. + * @param settings A mapping of setting names to setting values. * */ public void setConfigSettings(String moduleName, Map settings) { ModuleSettings.setConfigSettings(moduleName, settings); } + + /** + * Gets the current SleuthKit case. The SleuthKit case is the case database. + * + * @return The current case database. + * + * @deprecated Use getCaseDatabase instead. + */ + @Deprecated + public SleuthkitCase getCurrentSleuthkitCaseDb() { + return Case.getCurrentCase().getSleuthkitCase(); + } + + /** + * Get the current open case. + * + * @return The current case. + * + * @deprecated Use getCase instead. + */ + @Deprecated + public Case getCurrentCase() { + return Case.getCurrentCase(); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java index 72bc963fe7..d18faeee7b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java @@ -84,7 +84,7 @@ public final class RunIngestAction extends CallableSystemAction implements Prese @Override public boolean isEnabled() { try { - Case openCase = Case.getOpenCase(); + Case openCase = Case.getCurrentCaseThrows(); return openCase.hasData(); } catch (NoCurrentCaseException ex) { return false; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java index d58c57da72..c03abd577b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java @@ -49,7 +49,7 @@ final class RunIngestSubMenu extends JMenuItem implements DynamicMenuContent { List dataSources = new ArrayList<>(); try { - dataSources = Case.getOpenCase().getDataSources(); + dataSources = Case.getCurrentCaseThrows().getDataSources(); } catch (IllegalStateException ex) { // No open Cases, create a disabled empty menu return getEmpty(); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java index 9e05d538de..7d57b420ab 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java @@ -94,7 +94,7 @@ public final class BlackboardPostEvent extends AutopsyEvent implements Serializa SerializableEventData data = (SerializableEventData) super.getOldValue(); Collection artifacts = new ArrayList<>(); for (Long id : data.artifactIds) { - artifacts.add(Case.getOpenCase().getSleuthkitCase().getBlackboardArtifact(id)); + artifacts.add(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(id)); } eventData = new ModuleDataEvent(data.moduleName, data.artifactTypeId, !artifacts.isEmpty() ? artifacts : null); return eventData; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/ContentChangedEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/ContentChangedEvent.java index d22cefb5ac..fbb842d9d2 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/events/ContentChangedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/events/ContentChangedEvent.java @@ -86,7 +86,7 @@ public final class ContentChangedEvent extends AutopsyEvent implements Serializa } try { SerializableEventData data = (SerializableEventData) super.getOldValue(); - Content content = Case.getOpenCase().getSleuthkitCase().getContentById(data.contentId); + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(data.contentId); eventData = new ModuleContentEvent(data.moduleName, content); return eventData; } catch (NoCurrentCaseException | TskCoreException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisEvent.java index 100db13067..fb7d2f2f56 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisEvent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisEvent.java @@ -97,7 +97,7 @@ public abstract class DataSourceAnalysisEvent extends AutopsyEvent implements Se } try { long id = (Long) super.getNewValue(); - dataSource = Case.getOpenCase().getSleuthkitCase().getContentById(id); + dataSource = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(id); return dataSource; } catch (NoCurrentCaseException | TskCoreException ex) { logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/FileAnalyzedEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/FileAnalyzedEvent.java index 6845fcf391..42488c69de 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/events/FileAnalyzedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/events/FileAnalyzedEvent.java @@ -77,7 +77,7 @@ public final class FileAnalyzedEvent extends AutopsyEvent implements Serializabl } try { long id = (Long) super.getOldValue(); - file = Case.getOpenCase().getSleuthkitCase().getAbstractFileById(id); + file = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(id); return file; } catch (NoCurrentCaseException | TskCoreException ex) { logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java b/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java index a677f8042a..b025cebf79 100644 --- a/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java +++ b/Core/src/org/sleuthkit/autopsy/menuactions/DataContentDynamicMenu.java @@ -52,7 +52,7 @@ class DataContentDynamicMenu extends JMenuItem implements DynamicMenuContent { defaultItem.addActionListener(new OpenTopComponentAction(contentWin)); try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); defaultItem.setEnabled(currentCase.hasData()); } catch (NoCurrentCaseException ex) { defaultItem.setEnabled(false); // disable the menu when no case is opened diff --git a/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java b/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java index 38377d0f28..8dac009bf0 100644 --- a/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java +++ b/Core/src/org/sleuthkit/autopsy/menuactions/DataExplorerDynamicMenu.java @@ -55,7 +55,7 @@ class DataExplorerDynamicMenu extends JMenuItem implements DynamicMenuContent { item.addActionListener(new OpenTopComponentAction(explorerWin)); try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); item.setEnabled(currentCase.hasData()); } catch (NoCurrentCaseException ex) { item.setEnabled(false); // disable the menu when no case is opened diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 331b6d8830..5e67e9da8b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -67,7 +67,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * is used to write the extracted (derived) files to local storage. */ try { - final Case currentCase = Case.getOpenCase(); + final Case currentCase = Case.getCurrentCaseThrows(); moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); } catch (NoCurrentCaseException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java index bae9570ab5..722f211951 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java @@ -111,8 +111,8 @@ public class ExtractArchiveWithPasswordAction extends AbstractAction { protected Boolean doInBackground() { boolean done = false; try { - String moduleDirRelative = Paths.get(Case.getOpenCase().getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); - String moduleDirAbsolute = Paths.get(Case.getOpenCase().getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + String moduleDirRelative = Paths.get(Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + String moduleDirAbsolute = Paths.get(Case.getCurrentCaseThrows().getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); /* * Construct a file type detector. */ diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java index 23e33dc180..b123197f0a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java @@ -118,7 +118,7 @@ class MSOfficeEmbeddedContentExtractor { MSOfficeEmbeddedContentExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws NoCurrentCaseException { - this.fileManager = Case.getOpenCase().getServices().getFileManager(); + this.fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); this.services = IngestServices.getInstance(); this.context = context; this.fileTypeDetector = fileTypeDetector; diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 5b405e3939..2171f98c54 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -284,7 +284,7 @@ class SevenZipExtractor { //check if already has derived files, skip //check if local unpacked dir exists if (archiveFile.hasChildren() && new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) { - return Case.getOpenCase().getServices().getFileManager().findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath); + return Case.getCurrentCaseThrows().getServices().getFileManager().findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath); } return new ArrayList<>(); } @@ -494,7 +494,7 @@ class SevenZipExtractor { final long archiveId = archiveFile.getId(); SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = null; try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS unpackSuccessful = false; @@ -997,7 +997,7 @@ class SevenZipExtractor { * files for the entire hierarchy */ void updateOrAddFileToCaseRec(HashMap statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException { - final FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + final FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); for (UnpackedNode child : rootNode.children) { updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java new file mode 100644 index 0000000000..c659e3f051 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -0,0 +1,174 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.Volume; +import org.sleuthkit.datamodel.VolumeSystem; + +/** + * Data source module to detect encryption. + */ +final class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule { + + private final IngestServices services = IngestServices.getInstance(); + private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); + private Blackboard blackboard; + private double calculatedEntropy; + private final double minimumEntropy; + + /** + * Create an EncryptionDetectionDataSourceIngestModule object that will + * detect volumes that are encrypted and create blackboard artifacts as + * appropriate. The supplied EncryptionDetectionIngestJobSettings object is + * used to configure the module. + */ + EncryptionDetectionDataSourceIngestModule(EncryptionDetectionIngestJobSettings settings) { + minimumEntropy = settings.getMinimumEntropy(); + } + + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + validateSettings(); + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard(); + } + + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { + + try { + if (dataSource instanceof Image) { + List volumeSystems = ((Image) dataSource).getVolumeSystems(); + for (VolumeSystem volumeSystem : volumeSystems) { + for (Volume volume : volumeSystem.getVolumes()) { + if (isVolumeEncrypted(volume)) { + return flagVolume(volume); + } + } + } + } + } catch (ReadContentInputStream.ReadContentInputStreamException ex) { + logger.log(Level.WARNING, String.format("Unable to read data source '%s'", dataSource.getName()), ex); + return IngestModule.ProcessResult.ERROR; + } catch (IOException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Unable to process data source '%s'", dataSource.getName()), ex); + return IngestModule.ProcessResult.ERROR; + } + + return IngestModule.ProcessResult.OK; + } + + /** + * Validate the relevant settings for the + * EncryptionDetectionDataSourceIngestModule + * + * @throws IngestModule.IngestModuleException If the input is empty, + * invalid, or out of range. + * + */ + private void validateSettings() throws IngestModule.IngestModuleException { + EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); + } + + /** + * Create a blackboard artifact. + * + * @param The volume to be processed. + * + * @return 'OK' if the volume was processed successfully, or 'ERROR' if + * there was a problem. + */ + private IngestModule.ProcessResult flagVolume(Volume volume) { + try { + BlackboardArtifact artifact = volume.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + + try { + /* + * Index the artifact for keyword search. + */ + blackboard.publishArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + } + + /* + * Send an event to update the view with the new result. + */ + services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact))); + + /* + * Make an ingest inbox message. + */ + StringBuilder detailsSb = new StringBuilder(""); + detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()).append("
\n"); + detailsSb.append("Entropy: ").append(calculatedEntropy); + + services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), + "Encryption Detected Match: " + volume.getName(), + detailsSb.toString(), + volume.getName(), + artifact)); + + return IngestModule.ProcessResult.OK; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", volume.getName()), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + + /** + * This method checks if the Volume input is encrypted. Initial + * qualifications require that the Volume not have a file system. + * + * @param volume Volume to be checked. + * + * @return True if the Volume is encrypted. + */ + private boolean isVolumeEncrypted(Volume volume) throws ReadContentInputStream.ReadContentInputStreamException, IOException, TskCoreException { + /* + * Criteria for the checks in this method are partially based on + * http://www.forensicswiki.org/wiki/TrueCrypt#Detection + */ + if (volume.getFileSystems().isEmpty()) { + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume); + if (calculatedEntropy >= minimumEntropy) { + return true; + } + } + return false; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 9970a2613f..0526be93b7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -18,12 +18,25 @@ */ package org.sleuthkit.autopsy.modules.encryptiondetection; -import java.io.BufferedInputStream; +import com.healthmarketscience.jackcess.CryptCodecProvider; +import com.healthmarketscience.jackcess.Database; +import com.healthmarketscience.jackcess.DatabaseBuilder; +import com.healthmarketscience.jackcess.InvalidCredentialsException; +import com.healthmarketscience.jackcess.impl.CodecProvider; +import com.healthmarketscience.jackcess.impl.UnsupportedCodecException; +import com.healthmarketscience.jackcess.util.MemFileChannel; import java.io.IOException; -import java.io.InputStream; import java.util.Collections; import java.util.logging.Level; -import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.ReadContentInputStream; +import java.io.BufferedInputStream; +import java.io.InputStream; +import org.apache.tika.exception.EncryptedDocumentException; +import org.apache.tika.exception.TikaException; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.AutoDetectParser; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.sax.BodyContentHandler; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -37,28 +50,27 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + + /** - * File ingest module to detect encryption. + * File ingest module to detect encryption and password protection. */ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { - static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5; - static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB; - static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true; - static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true; - - static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; - static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; - static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; - private static final int FILE_SIZE_MODULUS = 512; - private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) - private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; + + private static final String MIME_TYPE_OOXML_PROTECTED = "application/x-ooxml-protected"; + private static final String MIME_TYPE_MSWORD = "application/msword"; + private static final String MIME_TYPE_MSEXCEL = "application/vnd.ms-excel"; + private static final String MIME_TYPE_MSPOWERPOINT = "application/vnd.ms-powerpoint"; + private static final String MIME_TYPE_MSACCESS = "application/x-msaccess"; + private static final String MIME_TYPE_PDF = "application/pdf"; private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); @@ -73,9 +85,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /** * Create a EncryptionDetectionFileIngestModule object that will detect - * files that are encrypted and create blackboard artifacts as appropriate. - * The supplied EncryptionDetectionIngestJobSettings object is used to - * configure the module. + * files that are either encrypted or password protected and create + * blackboard artifacts as appropriate. The supplied + * EncryptionDetectionIngestJobSettings object is used to configure the + * module. */ EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) { minimumEntropy = settings.getMinimumEntropy(); @@ -88,7 +101,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { try { validateSettings(); - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); fileTypeDetector = new FileTypeDetector(); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModule.IngestModuleException("Failed to create file type detector", ex); @@ -101,13 +114,37 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter public IngestModule.ProcessResult process(AbstractFile file) { try { - if (isFileEncrypted(file)) { - return flagFile(file); + /* + * Qualify the file type. + */ + if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR) + && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) { + /* + * Qualify the file against hash databases. + */ + if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + /* + * Qualify the MIME type. + */ + String mimeType = fileTypeDetector.getMIMEType(file); + if (mimeType.equals("application/octet-stream")) { + if (isFileEncryptionSuspected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + } + } else { + if (isFilePasswordProtected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + } + } + } } - } catch (ReadContentInputStreamException ex) { + } catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) { logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex); return IngestModule.ProcessResult.ERROR; - } catch (IOException | TskCoreException ex) { + } catch (IOException ex) { logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex); return IngestModule.ProcessResult.ERROR; } @@ -121,31 +158,23 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter * @throws IngestModule.IngestModuleException If the input is empty, * invalid, or out of range. */ - @NbBundle.Messages({ - "EncryptionDetectionFileIngestModule.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.", - "EncryptionDetectionFileIngestModule.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." - }) private void validateSettings() throws IngestModule.IngestModuleException { - if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { - throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput()); - } - - if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { - throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput()); - } + EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); + EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize); } /** * Create a blackboard artifact. * - * @param The file to be processed. + * @param file The file to be processed. + * @param artifactType The type of artifact to create. * * @return 'OK' if the file was processed successfully, or 'ERROR' if there * was a problem. */ - private IngestModule.ProcessResult flagFile(AbstractFile file) { + private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) { try { - BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + BlackboardArtifact artifact = file.newArtifact(artifactType); try { /* @@ -159,17 +188,19 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /* * Send an event to update the view with the new result. */ - services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact))); + services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact))); /* * Make an ingest inbox message. */ StringBuilder detailsSb = new StringBuilder(); - detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("
\n"); - detailsSb.append("Entropy: ").append(calculatedEntropy); + detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()); + if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) { + detailsSb.append("
\n").append("Entropy: ").append(calculatedEntropy); + } services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), - "Encryption Detected Match: " + file.getName(), + artifactType.getDisplayName() + " Match: " + file.getName(), detailsSb.toString(), file.getName(), artifact)); @@ -182,16 +213,135 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter } /** - * This method checks if the AbstractFile input is encrypted. Initial - * qualifications require that it be an actual file that is not known, meets - * file size requirements, and has a MIME type of - * 'application/octet-stream'. + * This method checks if the AbstractFile input is password protected. * * @param file AbstractFile to be checked. * - * @return True if the AbstractFile is encrypted. + * @return True if the file is password protected. + * + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. + * @throws SAXException If there was an issue parsing the + * file with Tika. + * @throws TikaException If there was an issue parsing the + * file with Tika. + * @throws UnsupportedCodecException If an Access database could not + * be opened by Jackcess due to + * unsupported encoding. */ - private boolean isFileEncrypted(AbstractFile file) throws ReadContentInputStreamException, IOException, TskCoreException { + private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException { + + boolean passwordProtected = false; + + switch (file.getMIMEType()) { + case MIME_TYPE_OOXML_PROTECTED: + /* + * Office Open XML files that are password protected can be + * determined so simply by checking the MIME type. + */ + passwordProtected = true; + break; + + case MIME_TYPE_MSWORD: + case MIME_TYPE_MSEXCEL: + case MIME_TYPE_MSPOWERPOINT: + case MIME_TYPE_PDF: { + /* + * A file of one of these types will be determined to be + * password protected or not by attempting to parse it via Tika. + */ + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(file); + bin = new BufferedInputStream(in); + ContentHandler handler = new BodyContentHandler(-1); + Metadata metadata = new Metadata(); + metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName()); + AutoDetectParser parser = new AutoDetectParser(); + parser.parse(bin, handler, metadata, new ParseContext()); + } catch (EncryptedDocumentException ex) { + /* + * File is determined to be password protected. + */ + passwordProtected = true; + } finally { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } + break; + } + + case MIME_TYPE_MSACCESS: { + /* + * Access databases are determined to be password protected + * using Jackcess. If the database can be opened, the password + * is read from it to see if it's null. If the database can not + * be opened due to an InvalidCredentialException being thrown, + * it is automatically determined to be password protected. + */ + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(file); + bin = new BufferedInputStream(in); + MemFileChannel memFileChannel = MemFileChannel.newChannel(bin); + CodecProvider codecProvider = new CryptCodecProvider(); + DatabaseBuilder databaseBuilder = new DatabaseBuilder(); + databaseBuilder.setChannel(memFileChannel); + databaseBuilder.setCodecProvider(codecProvider); + Database accessDatabase = databaseBuilder.open(); + /* + * No exception has been thrown at this point, so the file + * is either a JET database, or an unprotected ACE database. + * Read the password from the database to see if it exists. + */ + if (accessDatabase.getDatabasePassword() != null) { + passwordProtected = true; + } + } catch (InvalidCredentialsException ex) { + /* + * The ACE database is determined to be password protected. + */ + passwordProtected = true; + } finally { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } + } + } + + return passwordProtected; + } + + /** + * This method checks if the AbstractFile input is encrypted. It must meet + * file size requirements before its entropy is calculated. If the entropy + * result meets the minimum entropy value set, the file will be considered + * to be possibly encrypted. + * + * @param file AbstractFile to be checked. + * + * @return True if encryption is suspected. + * + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. + */ + private boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException { /* * Criteria for the checks in this method are partially based on * http://www.forensicswiki.org/wiki/TrueCrypt#Detection @@ -200,99 +350,20 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter boolean possiblyEncrypted = false; /* - * Qualify the file type. + * Qualify the size. */ - if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR) - && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) { - /* - * Qualify the file against hash databases. - */ - if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + long contentSize = file.getSize(); + if (contentSize >= minimumFileSize) { + if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) { /* - * Qualify the size. + * Qualify the entropy. */ - long contentSize = file.getSize(); - if (contentSize >= minimumFileSize) { - if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) { - /* - * Qualify the MIME type. - */ - String mimeType = fileTypeDetector.getMIMEType(file); - if (mimeType.equals("application/octet-stream")) { - possiblyEncrypted = true; - } - } + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file); + if (calculatedEntropy >= minimumEntropy) { + possiblyEncrypted = true; } } } - - if (possiblyEncrypted) { - calculatedEntropy = calculateEntropy(file); - if (calculatedEntropy >= minimumEntropy) { - return true; - } - } - - return false; - } - - /** - * Calculate the entropy of the file. The result is used to qualify the file - * as an encrypted file. - * - * @param file The file to be calculated against. - * - * @return The entropy of the file. - * - * @throws IOException If there is a failure closing or reading from the - * InputStream. - */ - private double calculateEntropy(AbstractFile file) throws ReadContentInputStreamException, IOException { - /* - * Logic in this method is based on - * https://github.com/willjasen/entropy/blob/master/entropy.java - */ - - InputStream in = null; - BufferedInputStream bin = null; - - try { - in = new ReadContentInputStream(file); - bin = new BufferedInputStream(in); - - /* - * Determine the number of times each byte value appears. - */ - int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; - int readByte; - while ((readByte = bin.read()) != -1) { - byteOccurences[readByte]++; - } - - /* - * Calculate the entropy based on the byte occurence counts. - */ - long dataLength = file.getSize() - 1; - double entropyAccumulator = 0; - for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { - if (byteOccurences[i] > 0) { - double byteProbability = (double) byteOccurences[i] / (double) dataLength; - entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); - } - } - - return -entropyAccumulator; - - } finally { - if (in != null) { - in.close(); - } - if (bin != null) { - bin.close(); - } - } + return possiblyEncrypted; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java index 2aa6ad860d..5acb97a518 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java @@ -26,7 +26,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings { private static final long serialVersionUID = 1L; - + private static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5; + private static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB; + private static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true; + private static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true; private double minimumEntropy; private int minimumFileSize; private boolean fileSizeMultipleEnforced; @@ -36,10 +39,10 @@ final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJo * Instantiate the ingest job settings with default values. */ EncryptionDetectionIngestJobSettings() { - this.minimumEntropy = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_ENTROPY; - this.minimumFileSize = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_FILE_SIZE; - this.fileSizeMultipleEnforced = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; - this.slackFilesAllowed = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_SLACK_FILES_ALLOWED; + this.minimumEntropy = DEFAULT_CONFIG_MINIMUM_ENTROPY; + this.minimumFileSize = DEFAULT_CONFIG_MINIMUM_FILE_SIZE; + this.fileSizeMultipleEnforced = DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; + this.slackFilesAllowed = DEFAULT_CONFIG_SLACK_FILES_ALLOWED; } /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java old mode 100644 new mode 100755 index 27549f648f..7a2d486841 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** - * A factory that creates file ingest modules that detect encryption. + * A factory that creates file ingest modules that detect encryption and + * password protection. */ @ServiceProvider(service = IngestModuleFactory.class) @Messages({ @@ -106,11 +107,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory { @Override public boolean isDataSourceIngestModuleFactory() { - return false; + return true; } @Override public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { - throw new UnsupportedOperationException(); + if (!(settings instanceof EncryptionDetectionIngestJobSettings)) { + throw new IllegalArgumentException("Expected settings argument to be an instance of EncryptionDetectionIngestJobSettings."); + } + return new EncryptionDetectionDataSourceIngestModule((EncryptionDetectionIngestJobSettings) settings); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java new file mode 100644 index 0000000000..99dbc0aaeb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java @@ -0,0 +1,131 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.Content; + +/** + * Class containing common methods concerning the Encryption Detection module. + */ +final class EncryptionDetectionTools { + + private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) + private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; + static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; + static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; + static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; + + @NbBundle.Messages({ + "EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0." + }) + /** + * Check if the minimum entropy setting is in the accepted range for this + * module. + */ + static void validateMinEntropyValue(double minimumEntropy) throws IngestModule.IngestModuleException { + if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { + throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumEntropyInput()); + } + } + + @NbBundle.Messages({ + "EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." + }) + /** + * Check if the minimum file size setting is in the accepted range for this + * module. + */ + static void validateMinFileSizeValue(int minimumFileSize) throws IngestModule.IngestModuleException { + if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { + throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumFileSizeInput()); + } + } + + + /** + * Calculate the entropy of the content. The result is used to qualify the + * content as possibly encrypted. + * + * @param content The content to be calculated against. + * + * @return The entropy of the content. + * + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. + */ + static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException { + /* + * Logic in this method is based on + * https://github.com/willjasen/entropy/blob/master/entropy.java + */ + + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(content); + bin = new BufferedInputStream(in); + + /* + * Determine the number of times each byte value appears. + */ + int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; + int readByte; + while ((readByte = bin.read()) != -1) { + byteOccurences[readByte]++; + } + + /* + * Calculate the entropy based on the byte occurence counts. + */ + long dataLength = content.getSize() - 1; + double entropyAccumulator = 0; + for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { + if (byteOccurences[i] > 0) { + double byteProbability = (double) byteOccurences[i] / (double) dataLength; + entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); + } + } + + return -entropyAccumulator; + + } finally { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } + } + + /** + * Private constructor for Encryption Detection Tools class. + */ + private EncryptionDetectionTools() { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java index a10886df28..9578cbaa23 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java @@ -104,7 +104,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule { @Override public ProcessResult process(AbstractFile content) { try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java index 7683cca305..f1eb697c2c 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java @@ -110,7 +110,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule { @Messages({"FileExtMismatchIngestModule.indexError.message=Failed to index file extension mismatch artifact for keyword search."}) public ProcessResult process(AbstractFile abstractFile) { try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java index 2e910dc1a3..88d2cad506 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java @@ -157,7 +157,7 @@ public class FileTypeIdIngestModule implements FileIngestModule { attributes.add(ruleNameAttribute); artifact.addAttributes(attributes); try { - Case.getOpenCase().getSleuthkitCase().getBlackboard().publishArtifact(artifact); + Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().publishArtifact(artifact); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, String.format("Unable to index TSK_INTERESTING_FILE_HIT blackboard artifact %d (file obj_id=%d)", artifact.getArtifactID(), file.getId()), ex); //NON-NLS } catch (NoCurrentCaseException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index a556a969fa..36bd422b65 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -32,6 +32,8 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; @@ -90,7 +92,7 @@ public class HashDbIngestModule implements FileIngestModule { HashDbIngestModule(HashLookupModuleSettings settings) throws NoCurrentCaseException { this.settings = settings; - skCase = Case.getOpenCase().getSleuthkitCase(); + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); } @Override @@ -147,7 +149,7 @@ public class HashDbIngestModule implements FileIngestModule { @Override public ProcessResult process(AbstractFile file) { try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -182,8 +184,20 @@ public class HashDbIngestModule implements FileIngestModule { String md5Hash = file.getMd5Hash(); if (md5Hash == null || md5Hash.isEmpty()) { try { + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); long calcstart = System.currentTimeMillis(); md5Hash = HashUtility.calculateMd5Hash(file); + if (file.getSize() > 0) { + // Surprisingly, the hash calculation does not seem to be correlated that + // strongly with file size until the files get large. + // Only normalize if the file size is greater than ~1MB. + if (file.getSize() < 1000000) { + EnterpriseHealthMonitor.submitTimingMetric(metric); + } else { + // In testing, this normalization gave reasonable resuls + EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); + } + } file.setMd5Hash(md5Hash); long delta = (System.currentTimeMillis() - calcstart); totals.totalCalctime.addAndGet(delta); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearcher.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearcher.java index 213400925f..91d1d08e36 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearcher.java @@ -46,7 +46,7 @@ class HashDbSearcher { * @return a List of all FsContent with the given hash */ static List findFilesByMd5(String md5Hash) throws NoCurrentCaseException { - final Case currentCase = Case.getOpenCase(); + final Case currentCase = Case.getCurrentCaseThrows(); final SleuthkitCase skCase = currentCase.getSleuthkitCase(); return skCase.findFilesByMd5(md5Hash); } @@ -123,7 +123,7 @@ class HashDbSearcher { * @return true if the search feature is ready. */ static boolean allFilesMd5Hashed() throws NoCurrentCaseException { - final Case currentCase = Case.getOpenCase(); + final Case currentCase = Case.getCurrentCaseThrows(); final SleuthkitCase skCase = currentCase.getSleuthkitCase(); return skCase.allFilesMd5Hashed(); } @@ -134,7 +134,7 @@ class HashDbSearcher { * @return the number of files with an MD5 */ static int countFilesMd5Hashed() throws NoCurrentCaseException { - final Case currentCase = Case.getOpenCase(); + final Case currentCase = Case.getCurrentCaseThrows(); final SleuthkitCase skCase = currentCase.getSleuthkitCase(); return skCase.countFilesMd5Hashed(); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java index 2296c2a264..e56255f898 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java @@ -65,7 +65,7 @@ final class CallLogAnalyzer { public void findCallLogs(IngestJobContext context) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; @@ -80,7 +80,7 @@ final class CallLogAnalyzer { } for (AbstractFile file : absFiles) { try { - jFile = new java.io.File(Case.getOpenCase().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); + jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); dbPath = jFile.toString(); //path of file as string fileId = file.getId(); ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled); @@ -117,7 +117,7 @@ final class CallLogAnalyzer { Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java index aa68057d87..f9f3e2e484 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java @@ -71,7 +71,7 @@ final class ContactAnalyzer { public void findContacts(IngestJobContext context) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; @@ -116,7 +116,7 @@ final class ContactAnalyzer { } Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java index 35e7baa2c8..da87b8882b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java @@ -68,7 +68,7 @@ class TextMessageAnalyzer { void findTexts(IngestJobContext context) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; @@ -82,7 +82,7 @@ class TextMessageAnalyzer { } for (AbstractFile file : absFiles) { try { - jFile = new java.io.File(Case.getOpenCase().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); + jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); dbPath = jFile.toString(); //path of file as string fileId = file.getId(); ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled); @@ -112,7 +112,7 @@ class TextMessageAnalyzer { } Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java index c6f25b119c..d3a936a83d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java @@ -107,7 +107,7 @@ final class FilesIdentifierIngestModule implements FileIngestModule { @Messages({"FilesIdentifierIngestModule.indexError.message=Failed to index interesting file hit artifact for keyword search."}) public ProcessResult process(AbstractFile file) { try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index 446da55940..e98e15a331 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -427,7 +427,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { synchronized Path createModuleOutputDirectoryForCase() throws IngestModule.IngestModuleException { Path path; try { - path = Paths.get(Case.getOpenCase().getModuleDirectory(), PhotoRecCarverIngestModuleFactory.getModuleName()); + path = Paths.get(Case.getCurrentCaseThrows().getModuleDirectory(), PhotoRecCarverIngestModuleFactory.getModuleName()); } catch (NoCurrentCaseException ex) { throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java index 6c1975f5a8..967a3c41ab 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java @@ -100,7 +100,7 @@ class PhotoRecCarverOutputParser { NodeList fileRanges; Element entry; Path filePath; - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); // create and initialize the list to put into the database List carvedFiles = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAccountObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAccountObj.java index 5174e479eb..35bfd82eff 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAccountObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAccountObj.java @@ -105,7 +105,7 @@ class EvalAccountObj extends EvaluatableObject { try { List finalHits = new ArrayList(); - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); List artList = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAddressObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAddressObj.java index 4141ba874f..b37f9dd304 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAddressObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalAddressObj.java @@ -57,7 +57,7 @@ class EvalAddressObj extends EvaluatableObject { Case case1; try { - case1 = Case.getOpenCase(); + case1 = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return new ObservableResult(id, "Exception while getting open case.", //NON-NLS spacing, ObservableResult.ObservableState.FALSE, null); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalDomainObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalDomainObj.java index d06c5a19e0..e57ac8e067 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalDomainObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalDomainObj.java @@ -55,7 +55,7 @@ class EvalDomainObj extends EvaluatableObject { Case case1; try { - case1 = Case.getOpenCase(); + case1 = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return new ObservableResult(id, "Exception while getting open case.", //NON-NLS spacing, ObservableResult.ObservableState.FALSE, null); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalFileObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalFileObj.java index e99f34fe06..c5b3bae881 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalFileObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalFileObj.java @@ -62,7 +62,7 @@ class EvalFileObj extends EvaluatableObject { Case case1; try { - case1 = Case.getOpenCase(); + case1 = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return new ObservableResult(id, "Exception while getting open case.", //NON-NLS spacing, ObservableResult.ObservableState.FALSE, null); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalNetworkShareObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalNetworkShareObj.java index 7c1379e75f..7e6fa7c7b1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalNetworkShareObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalNetworkShareObj.java @@ -89,7 +89,7 @@ class EvalNetworkShareObj extends EvaluatableObject { try { List finalHits = new ArrayList(); - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); List artList = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_REMOTE_DRIVE); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java index 6040ea66e5..b5a4662ec6 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java @@ -348,7 +348,7 @@ class EvalRegistryObj extends EvaluatableObject { // Make the temp directory String tmpDir; try { - tmpDir = Case.getOpenCase().getTempDirectory() + File.separator + "STIX"; //NON-NLS + tmpDir = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + "STIX"; //NON-NLS } catch (NoCurrentCaseException ex) { throw new TskCoreException(ex.getLocalizedMessage()); } @@ -385,7 +385,7 @@ class EvalRegistryObj extends EvaluatableObject { List registryFiles = new ArrayList(); Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { throw new TskCoreException(ex.getLocalizedMessage()); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalSystemObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalSystemObj.java index bcfc5bc88e..73a7b1449f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalSystemObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalSystemObj.java @@ -136,7 +136,7 @@ class EvalSystemObj extends EvaluatableObject { setUnsupportedFieldWarnings(); try { - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); List osInfoList = OSUtility.getOSInfo(sleuthkitCase); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURIObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURIObj.java index 123468041d..0c0ddb6971 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURIObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURIObj.java @@ -56,7 +56,7 @@ class EvalURIObj extends EvaluatableObject { Case case1; try { - case1 = Case.getOpenCase(); + case1 = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return new ObservableResult(id, "Exception while getting open case: " + ex.getLocalizedMessage(), //NON-NLS spacing, ObservableResult.ObservableState.FALSE, null); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURLHistoryObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURLHistoryObj.java index 5cfa2adcec..5cf0b28213 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURLHistoryObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalURLHistoryObj.java @@ -139,7 +139,7 @@ class EvalURLHistoryObj extends EvaluatableObject { } try { - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); List artList = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY); @@ -232,7 +232,7 @@ class EvalURLHistoryObj extends EvaluatableObject { // It doesn't seem too useful, but we can just search for the browser name // if there aren't any URL entries try { - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); List artList = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvaluatableObject.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvaluatableObject.java index 5c3a4cc75a..1fd7a7bed1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvaluatableObject.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvaluatableObject.java @@ -101,7 +101,7 @@ abstract class EvaluatableObject { List hits = null; try { - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); String[] parts = item.getValue().toString().split("##comma##"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java index 3f295b5cd7..5daf23c217 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java @@ -186,7 +186,7 @@ public class STIXReportModule implements GeneralReportModule { // Set the progress bar to done. If any errors occurred along the way, modify // the "complete" message to indicate this. - Case.getOpenCase().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), ""); + Case.getCurrentCaseThrows().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), ""); if (hadErrors) { progressPanel.complete(ReportStatus.ERROR); progressPanel.updateStatusLabel( diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java b/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java index 3ca9af0ce4..082f77d113 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java @@ -51,7 +51,7 @@ class StixArtifactData { public StixArtifactData(long a_objId, String a_observableId, String a_objType) { try { - Case case1 = Case.getOpenCase(); + Case case1 = Case.getCurrentCaseThrows(); SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); file = sleuthkitCase.getAbstractFileById(a_objId); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -66,7 +66,7 @@ class StixArtifactData { public void createArtifact(String a_title) throws TskCoreException { Blackboard blackboard; try { - blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.StixArtifactData_noOpenCase_errMsg(), ex.getLocalizedMessage()); diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java index a6d22b4d6b..843b07a3d6 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java @@ -81,7 +81,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { this.context = context; long dataSourceObjId = context.getDataSource().getId(); try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); SleuthkitCase caseDb = currentCase.getSleuthkitCase(); DataSource dataSource = caseDb.getDataSource(dataSourceObjId); parentDeviceId = dataSource.getDeviceId(); @@ -234,7 +234,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { List vmFiles = new ArrayList<>(); for (String vmExtension : GeneralFilter.VIRTUAL_MACHINE_EXTS) { String searchString = "%" + vmExtension; // want a search string that looks like this "%.vmdk" - vmFiles.addAll(Case.getOpenCase().getServices().getFileManager().findFiles(dataSource, searchString)); + vmFiles.addAll(Case.getCurrentCaseThrows().getServices().getFileManager().findFiles(dataSource, searchString)); } return vmFiles; } @@ -275,7 +275,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { * Try to add the virtual machine file to the case as a data source. */ UUID taskId = UUID.randomUUID(); - Case.getOpenCase().notifyAddingDataSource(taskId); + Case.getCurrentCaseThrows().notifyAddingDataSource(taskId); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); AddDataSourceCallback dspCallback = new AddDataSourceCallback(vmFile); synchronized (this) { @@ -291,7 +291,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { * ingest context. */ if (!dspCallback.vmDataSources.isEmpty()) { - Case.getOpenCase().notifyDataSourceAdded(dspCallback.vmDataSources.get(0), taskId); + Case.getCurrentCaseThrows().notifyDataSourceAdded(dspCallback.vmDataSources.get(0), taskId); List dataSourceContent = new ArrayList<>(dspCallback.vmDataSources); IngestJobSettings ingestJobSettings = new IngestJobSettings(context.getExecutionContext()); for (String warning : ingestJobSettings.getWarnings()) { @@ -302,7 +302,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.addedVirtualMachineImage.message", vmFile.toString()))); IngestManager.getInstance().queueIngestJob(dataSourceContent, ingestJobSettings); } else { - Case.getOpenCase().notifyFailedAddingDataSource(taskId); + Case.getCurrentCaseThrows().notifyFailedAddingDataSource(taskId); } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java index 300a810def..50e9715a16 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java +++ b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java @@ -75,7 +75,7 @@ public class ArtifactSelectionDialog extends javax.swing.JDialog { BlackboardArtifact.ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getLabel(), BlackboardArtifact.ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getDisplayName())); // output is too unstructured for table review - artifactTypes = Case.getOpenCase().getSleuthkitCase().getArtifactTypesInUse(); + artifactTypes = Case.getCurrentCaseThrows().getSleuthkitCase().getArtifactTypesInUse(); artifactTypes.removeAll(doNotReport); Collections.sort(artifactTypes, new Comparator() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/report/FileReportText.java b/Core/src/org/sleuthkit/autopsy/report/FileReportText.java index 71cccf96ed..f96bad446b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/FileReportText.java +++ b/Core/src/org/sleuthkit/autopsy/report/FileReportText.java @@ -72,7 +72,7 @@ class FileReportText implements FileReportModule { if (out != null) { try { out.close(); - Case.getOpenCase().addReport(reportPath, NbBundle.getMessage(this.getClass(), + Case.getCurrentCaseThrows().addReport(reportPath, NbBundle.getMessage(this.getClass(), "FileReportText.getName.text"), ""); } catch (IOException ex) { logger.log(Level.WARNING, "Could not close output writer when ending report.", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportBodyFile.java b/Core/src/org/sleuthkit/autopsy/report/ReportBodyFile.java index 3a601fdd7f..4695418117 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportBodyFile.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportBodyFile.java @@ -75,7 +75,7 @@ class ReportBodyFile implements GeneralReportModule { public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { // Start the progress bar and setup the report try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; @@ -161,7 +161,7 @@ class ReportBodyFile implements GeneralReportModule { if (out != null) { out.flush(); out.close(); - Case.getOpenCase().addReport(reportPath, + Case.getCurrentCaseThrows().addReport(reportPath, NbBundle.getMessage(this.getClass(), "ReportBodyFile.generateReport.srcModuleName.text"), ""); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index b28ac5ade2..3dcc416ef3 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -113,7 +113,7 @@ class ReportExcel implements TableReportModule { try { out = new FileOutputStream(reportPath); wb.write(out); - Case.getOpenCase().addReport(reportPath, NbBundle.getMessage(this.getClass(), + Case.getCurrentCaseThrows().addReport(reportPath, NbBundle.getMessage(this.getClass(), "ReportExcel.endReport.srcModuleName.text"), ""); } catch (IOException ex) { logger.log(Level.SEVERE, "Failed to write Excel report.", ex); //NON-NLS @@ -305,7 +305,7 @@ class ReportExcel implements TableReportModule { private void writeSummaryWorksheet() { Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 127f537df4..aecfe0661a 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -228,7 +228,7 @@ class ReportGenerator { private List getFiles() { List absFiles; try { - SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); absFiles = skCase.findAllFilesWhere("meta_type != " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()); //NON-NLS return absFiles; } catch (TskCoreException | NoCurrentCaseException ex) { @@ -253,7 +253,7 @@ class ReportGenerator { private static String createReportDirectory(ReportModule module) throws IOException { Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { throw new IOException("Exception while getting open case.", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index fdc0457b1c..2f0cb5f770 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -104,7 +104,7 @@ class ReportHTML implements TableReportModule { // Refesh the member variables private void refresh() throws NoCurrentCaseException { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); skCase = currentCase.getSleuthkitCase(); dataTypes = new TreeMap<>(); @@ -890,7 +890,7 @@ class ReportHTML implements TableReportModule { String indexFilePath = path + "report.html"; //NON-NLS Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java index c5c57eccb8..3a092efc83 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java @@ -101,7 +101,7 @@ class ReportKML implements GeneralReportModule { @Override public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; @@ -387,7 +387,7 @@ class ReportKML implements GeneralReportModule { if (result == ReportProgressPanel.ReportStatus.ERROR) { prependedStatus = "Incomplete "; } - Case.getOpenCase().addReport(kmlFileFullPath, + Case.getCurrentCaseThrows().addReport(kmlFileFullPath, NbBundle.getMessage(this.getClass(), "ReportKML.genReport.srcModuleName.text"), prependedStatus + NbBundle.getMessage(this.getClass(), "ReportKML.genReport.reportName")); } catch (IOException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java index 5790a34de4..98160874c9 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java @@ -98,7 +98,7 @@ final class ReportVisualPanel2 extends JPanel { private void initTags() { List tagNamesInUse; try { - tagNamesInUse = Case.getOpenCase().getServices().getTagsManager().getTagNamesInUse(); + tagNamesInUse = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ReportVisualPanel2.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS return; @@ -137,7 +137,7 @@ final class ReportVisualPanel2 extends JPanel { private void initArtifactTypes() { try { - Case openCase = Case.getOpenCase(); + Case openCase = Case.getCurrentCaseThrows(); ArrayList doNotReport = new ArrayList<>(); doNotReport.add(new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO.getTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO.getLabel(), diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 673d5fa3d4..9d5d41c836 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -182,7 +182,7 @@ class TableReportGenerator { String accountDisplayname = accountTypeStr; if (accountTypeStr != null) { try { - Account.Type acctType = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager().getAccountType(accountTypeStr); + Account.Type acctType = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().getAccountType(accountTypeStr); if (acctType != null) { accountDisplayname = acctType.getDisplayName(); } @@ -268,7 +268,7 @@ class TableReportGenerator { // Get the content tags. List tags; try { - tags = Case.getOpenCase().getServices().getTagsManager().getAllContentTags(); + tags = Case.getCurrentCaseThrows().getServices().getTagsManager().getAllContentTags(); } catch (TskCoreException | NoCurrentCaseException ex) { errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetContentTags")); logger.log(Level.SEVERE, "failed to get content tags", ex); //NON-NLS @@ -361,7 +361,7 @@ class TableReportGenerator { List tags; try { - tags = Case.getOpenCase().getServices().getTagsManager().getAllBlackboardArtifactTags(); + tags = Case.getCurrentCaseThrows().getServices().getTagsManager().getAllBlackboardArtifactTags(); } catch (TskCoreException | NoCurrentCaseException ex) { errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBArtifactTags")); logger.log(Level.SEVERE, "failed to get blackboard artifact tags", ex); //NON-NLS @@ -453,7 +453,7 @@ class TableReportGenerator { private void checkIfTagHasImage(BlackboardArtifactTag artifactTag) { AbstractFile file; try { - file = Case.getOpenCase().getSleuthkitCase().getAbstractFileById(artifactTag.getArtifact().getObjectID()); + file = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(artifactTag.getArtifact().getObjectID()); } catch (TskCoreException | NoCurrentCaseException ex) { errorList.add( NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.errGetContentFromBBArtifact")); @@ -532,7 +532,7 @@ class TableReportGenerator { String orderByClause; Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { errorList.add(Bundle.ReportGenerator_errList_noOpenCase()); logger.log(Level.SEVERE, "Exception while getting open case: ", ex); //NON-NLS @@ -697,7 +697,7 @@ class TableReportGenerator { String orderByClause; Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { errorList.add(Bundle.ReportGenerator_errList_noOpenCase()); logger.log(Level.SEVERE, "Exception while getting open case: ", ex); //NON-NLS @@ -852,7 +852,7 @@ class TableReportGenerator { this.attributes = attrs; this.tags = tags; try { - this.content = Case.getOpenCase().getSleuthkitCase().getContentById(artifact.getObjectID()); + this.content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Could not get content from database", ex); } @@ -990,7 +990,7 @@ class TableReportGenerator { HashSet allTags = getTags(); try { - List contentTags = Case.getOpenCase().getServices().getTagsManager().getContentTagsByContent(content); + List contentTags = Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content); for (ContentTag ct : contentTags) { String notableString = ct.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; allTags.add(ct.getName().getDisplayName() + notableString); @@ -1026,8 +1026,8 @@ class TableReportGenerator { private List getFilteredArtifacts(BlackboardArtifact.Type type, HashSet tagNamesFilter) { List artifacts = new ArrayList<>(); try { - for (BlackboardArtifact artifact : Case.getOpenCase().getSleuthkitCase().getBlackboardArtifacts(type.getTypeID())) { - List tags = Case.getOpenCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact); + for (BlackboardArtifact artifact : Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifacts(type.getTypeID())) { + List tags = Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact); HashSet uniqueTagNames = new HashSet<>(); for (BlackboardArtifactTag tag : tags) { String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; @@ -1037,7 +1037,7 @@ class TableReportGenerator { continue; } try { - artifacts.add(new ArtifactData(artifact, Case.getOpenCase().getSleuthkitCase().getBlackboardAttributes(artifact), uniqueTagNames)); + artifacts.add(new ArtifactData(artifact, Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardAttributes(artifact), uniqueTagNames)); } catch (TskCoreException ex) { errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBAttribs")); logger.log(Level.SEVERE, "Failed to get Blackboard Attributes when generating report.", ex); //NON-NLS @@ -1627,7 +1627,7 @@ class TableReportGenerator { + //NON-NLS "WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId; //NON-NLS - try (SleuthkitCase.CaseDbQuery dbQuery = Case.getOpenCase().getSleuthkitCase().executeQuery(query)) { + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { ResultSet tagNameRows = dbQuery.getResultSet(); while (tagNameRows.next()) { uniqueTagNames.add(tagNameRows.getString("display_name")); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java index f64a14306f..eb8b8a289f 100644 --- a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java +++ b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java @@ -69,7 +69,7 @@ public class AddTaggedHashesToHashDb implements GeneralReportModule { public void generateReport(String reportPath, ReportProgressPanel progressPanel) { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), "No open Case", "Exception while getting open case.", JOptionPane.ERROR_MESSAGE); diff --git a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java index 44e0f93d75..06484468a4 100644 --- a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java @@ -68,7 +68,7 @@ class AddTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel { private void populateTagNameComponents() { // Get the tag names in use for the current case. try { - tagNames = Case.getOpenCase().getServices().getTagsManager().getTagNamesInUse(); + tagNames = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); } catch (TskCoreException ex) { Logger.getLogger(AddTaggedHashesToHashDbConfigPanel.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); JOptionPane.showMessageDialog(this, "Error getting tag names for case.", "Tag Names Not Found", JOptionPane.ERROR_MESSAGE); diff --git a/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java index fa8d029aca..bbeeb31648 100644 --- a/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java @@ -66,7 +66,7 @@ final class CustomArtifactType { * @throws BlackboardException If there is an error adding any of the types. */ static void addToCaseDatabase() throws Blackboard.BlackboardException, NoCurrentCaseException { - Blackboard blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); artifactType = blackboard.getOrAddArtifactType(ARTIFACT_TYPE_NAME, ARTIFACT_DISPLAY_NAME); intAttrType = blackboard.getOrAddAttributeType(INT_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER, INT_ATTR_DISPLAY_NAME); doubleAttrType = blackboard.getOrAddAttributeType(DOUBLE_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE, DOUBLE_ATTR_DISPLAY_NAME); diff --git a/Core/src/org/sleuthkit/autopsy/test/InterestingArtifactCreatorIngestModule.java b/Core/src/org/sleuthkit/autopsy/test/InterestingArtifactCreatorIngestModule.java index 85dd3ed021..ac70da8e6c 100644 --- a/Core/src/org/sleuthkit/autopsy/test/InterestingArtifactCreatorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/test/InterestingArtifactCreatorIngestModule.java @@ -54,7 +54,7 @@ final class InterestingArtifactCreatorIngestModule extends FileIngestModuleAdapt @Override public void startUp(IngestJobContext context) throws IngestModuleException { try { - Blackboard blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); artifactType = blackboard.getOrAddArtifactType(INT_ARTIFACT_TYPE_NAME, INT_ARTIFACT_DISPLAY_NAME); } catch (Blackboard.BlackboardException | NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.InterestingArtifactCreatorIngestModule_exceptionMessage_errorCreatingCustomType(), ex); @@ -76,7 +76,7 @@ final class InterestingArtifactCreatorIngestModule extends FileIngestModuleAdapt * type. */ int randomArtIndex = (int) (Math.random() * 3); - Blackboard blackboard = Case.getOpenCase().getSleuthkitCase().getBlackboard(); + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); BlackboardArtifact.Type artifactTypeBase = blackboard.getOrAddArtifactType(ARTIFACT_TYPE_NAMES[randomArtIndex], ARTIFACT_DISPLAY_NAMES[randomArtIndex]); BlackboardArtifact artifactBase = file.newArtifact(artifactTypeBase.getTypeID()); Collection baseAttributes = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index badce2b808..03ea78b33e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -83,7 +83,7 @@ public final class OpenTimelineAction extends CallableSystemAction { @Override public boolean isEnabled() { /** - * We used to also check if Case.getOpenCase().hasData() was true. We + * We used to also check if Case.getCurrentOpenCase().hasData() was true. We * disabled that check because if it is executed while a data source is * being added, it blocks the edt. We still do that in ImageGallery. */ @@ -119,7 +119,7 @@ public final class OpenTimelineAction extends CallableSystemAction { "OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."}) synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException { try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); if (currentCase.hasData() == false) { MessageNotifyUtil.Message.info(Bundle.OpenTimeLineAction_msgdlg_text()); logger.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS @@ -213,7 +213,7 @@ public final class OpenTimelineAction extends CallableSystemAction { private boolean tooManyFiles() { try { - return FILE_LIMIT < Case.getOpenCase().getSleuthkitCase().countFilesWhere("1 = 1"); + return FILE_LIMIT < Case.getCurrentCaseThrows().getSleuthkitCase().countFilesWhere("1 = 1"); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Can not open timeline with no case open.", ex); } catch (TskCoreException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java index a379fc7d8d..cc58dfd80a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -225,7 +225,7 @@ public final class PromptDialogManager { @NbBundle.Messages({ "PromptDialogManager.showTimeLineDisabledMessage.contentText=" - + "Timeline functionality is not available for Linux yet." + + "Timeline functionality is not available yet." + " Timeline will be disabled. ", "PromptDialogManager.showTimeLineDisabledMessage.headerText="}) static void showTimeLineDisabledMessage() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 0a4256deae..d944888071 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -728,7 +728,7 @@ public class TimeLineController { * already closed. */ try { - Case.getOpenCase(); + Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException notUsed) { // Case is closed, do nothing. return; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index e0b3a3e333..b7fe2de313 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -153,7 +153,7 @@ public class SaveSnapshotAsReport extends Action { try { //add main file as report to case - Case.getOpenCase().addReport(reportMainFilePath.toString(), Bundle.Timeline_ModuleName(), reportName); + Case.getCurrentCaseThrows().addReport(reportMainFilePath.toString(), Bundle.Timeline_ModuleName(), reportName); } catch (TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Failed to add " + reportMainFilePath.toString() + " to case as a report", ex); //NON_NLS new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_FailedToAddReport()).show(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index e1717fd7bd..418ec640be 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -83,11 +83,11 @@ public class EventNode extends DisplayableItemNode { "NodeProperty.displayName.known=Known", "NodeProperty.displayName.dateTime=Date/Time"}) protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set properties = s.get(Sheet.PROPERTIES); + Sheet sheet = super.createSheet(); + Sheet.Set properties = sheet.get(Sheet.PROPERTIES); if (properties == null) { properties = Sheet.createPropertiesSet(); - s.put(properties); + sheet.put(properties); } properties.put(new NodeProperty<>("icon", Bundle.NodeProperty_displayName_icon(), "icon", true)); // NON-NLS //gets overridden with icon @@ -97,7 +97,7 @@ public class EventNode extends DisplayableItemNode { properties.put(new NodeProperty<>("eventSubType", Bundle.NodeProperty_displayName_subType(), "sub type", event.getEventType().getDisplayName())); // NON-NLS properties.put(new NodeProperty<>("Known", Bundle.NodeProperty_displayName_known(), "known", event.getKnown().toString())); // NON-NLS - return s; + return sheet; } /** @@ -224,7 +224,7 @@ public class EventNode extends DisplayableItemNode { */ final SingleEvent eventById = eventsModel.getEventById(eventID); - SleuthkitCase sleuthkitCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); if (eventById.getArtifactID().isPresent()) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 4395f08feb..a9427aa962 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -60,7 +60,7 @@ public class EventRootNode extends DisplayableItemNode { } @Override - public T accept(DisplayableItemNodeVisitor v) { + public T accept(DisplayableItemNodeVisitor visitor) { return null; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java index 5f6b0a73c4..1b2915dfde 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java @@ -28,8 +28,6 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; - -import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.function.Supplier; import javafx.application.Platform; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 105f13146a..99dbd9204d 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -2344,7 +2344,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a case from an Autopsy case // The case may already be in the database - the result is the same either way try { - caseB = EamDb.getInstance().newCase(Case.getOpenCase()); + caseB = EamDb.getInstance().newCase(Case.getCurrentCaseThrows()); assertTrue("Failed to create correlation case from Autopsy case", caseB != null); } catch (EamDbException | NoCurrentCaseException ex) { Exceptions.printStackTrace(ex); @@ -2413,7 +2413,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting a case from an Autopsy case try { - CorrelationCase tempCase = EamDb.getInstance().getCase(Case.getOpenCase()); + CorrelationCase tempCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows()); assertTrue("getCase returned null for current Autopsy case", tempCase != null); } catch (EamDbException | NoCurrentCaseException ex) { Exceptions.printStackTrace(ex); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java new file mode 100755 index 0000000000..36ecd56d8f --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -0,0 +1,278 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.ingest; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import static junit.framework.Assert.assertEquals; +import junit.framework.Test; +import org.netbeans.junit.NbModuleSuite; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; +import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.testutils.CaseUtils; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; + +public class EmbeddedFileTest extends NbTestCase { + + private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "EmbeddedFileTest"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; + private static final int DEEP_FOLDER_COUNT = 25; + private Case openCase; + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EmbeddedFileTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + public EmbeddedFileTest(String name) { + super(name); + } + + @Override + public void setUp() { + CaseUtils.createCase(CASE_DIRECTORY_PATH); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + + try { + openCase = Case.getOpenCase(); + } catch (NoCurrentCaseException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + IngestModuleTemplate embeddedTemplate = IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()); + IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); + + ArrayList templates = new ArrayList<>(); + templates.add(embeddedTemplate); + templates.add(hashLookupTemplate); + IngestJobSettings ingestJobSettings = new IngestJobSettings(EmbeddedFileTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + + try { + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + @Override + public void tearDown() { + CaseUtils.closeCase(); + CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH); + } + + public void testEncryption() { + try { + List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'"); + String protectedName1 = "password_protected.zip"; + String protectedName2 = "level1_protected.zip"; + String protectedName3 = "42.zip"; + assertEquals(2207, results.size()); + int passwdProtectedZips = 0; + for (AbstractFile file : results) { + //.zip file has artifact TSK_ENCRYPTION_DETECTED + if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)){ + ArrayList artifacts = file.getAllArtifacts(); + assertEquals(1, artifacts.size()); + for (BlackboardArtifact artifact : artifacts) { + assertEquals(artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()); + passwdProtectedZips++; + } + } else {//No other files have artifact defined + assertEquals(0, file.getAllArtifacts().size()); + } + + + } + //Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing. + assertEquals(3, passwdProtectedZips); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testBigFolder() { + final int numOfFilesToTest = 1000; + try { + //Get all files under 'big folder' directory except '.' '..' 'slack' files + List results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%big folder/' and name != '.' and name != '..' and extension NOT LIKE '%slack'"); + assertEquals(numOfFilesToTest, results.size()); //There are 1000 files + int numOfFilesTested = 0; + for (AbstractFile file : results) { + String fileName = file.getName(); + //File name should like file1.txt, file2.txt ... file1000.txt + String errMsg = String.format("File name %s doesn't follow the expected naming convention: fileNaturalNumber.txt, eg. file234.txt.", fileName); + assertTrue(errMsg, file.getName().matches("file[1-9]\\d*.txt")); + String hashValue = file.getMd5Hash(); + //All files have the same hash value + assertEquals(HASH_VALUE, hashValue); + numOfFilesTested++; + } + //Make sure 1000 files have been tested + assertEquals(numOfFilesToTest, numOfFilesTested); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testDeepFolder() { + try { + //Get all files under 'deep folder' directory except '.' '..' + List results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%deep folder/' and name != '.' and name != '..'"); + assertEquals(1, results.size()); + StringBuffer dirReached = new StringBuffer(); + ArrayList fileReached = new ArrayList<>(); + checkEachFileInDeepFolder(results.get(0), dirReached, fileReached, 0); + //Check that all 25 folders/files have been reached + assertEquals(DEEP_FOLDER_COUNT, fileReached.size()); + //Make sure the test reached the last directory 'dir25'. The whole directory is dir1/dir2...dir24/dir25/ + assertTrue(dirReached.toString().startsWith("dir1/dir2/")); + assertTrue(dirReached.toString().endsWith("dir24/dir25/")); + //Make sure the test reached the last file.txt in dir1/dir2...dir24/dir25/ + assertTrue(fileReached.get(0).endsWith(dirReached.toString() + "file.txt")); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testEmbeddedFile() { + try { + //Query level3.txt under '/ZIP/embedded/level3.zip/' + List results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level3.txt' and parent_path = '/ZIP/embedded/level3.zip/'"); + assertEquals(1, results.size()); + + //Query level2.txt under '/ZIP/embedded/level3.zip/level2.zip/' + results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level2.txt' and parent_path = '/ZIP/embedded/level3.zip/level2.zip/'"); + assertEquals(1, results.size()); + + //Query level1.txt under '/ZIP/embedded/level3.zip/level2.zip/level1.zip/' + results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level1.txt' and parent_path = '/ZIP/embedded/level3.zip/level2.zip/level1.zip/'"); + assertEquals(1, results.size()); + + //Confirm that we can reach level1.txt from the embedded folder + results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%embedded/' and name != '.' and name != '..' and extension NOT LIKE '%slack%'"); + assertEquals(1, results.size()); + assertTrue(checkFileInEmbeddedFolder(results.get(0))); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testContent() { + final int numOfFilesToTest = 1029; + try { + //All files with txt extension should have the same hash value, + //except the zip file with txt extension and the .txt files extracted from password protected zip shouldn't have hash value + List results = openCase.getSleuthkitCase().findAllFilesWhere( + "extension = 'txt' and name != 'zipFileWithTxtExtension.txt' and parent_path NOT LIKE '%_protected%'"); + assertEquals(numOfFilesToTest, results.size()); + int numOfHashTested = 0; + for (AbstractFile file : results) { + String fileName = file.getName(); + String errMsg = String.format("File name %s doesn't have the extected hash value %s.", fileName, HASH_VALUE); + assertEquals(errMsg, HASH_VALUE, file.getMd5Hash()); + numOfHashTested++; + } + //Make sure the hash value of 1029 files have been tested + assertEquals(numOfFilesToTest, numOfHashTested); + + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + public void testExtension() { + try { + //Query zipFileWithTxtExtension.txt at extension folder + List results = openCase.getSleuthkitCase().findAllFilesWhere("extension = 'txt' and parent_path = '/ZIP/extension/zipFileWithTxtExtension.txt/'"); + assertEquals(1, results.size()); + assertEquals("file.txt wasn't extracted from the file: zipFileWithTxtExtension.txt", "file.txt", results.get(0).getName()); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + private void checkEachFileInDeepFolder(AbstractFile file, StringBuffer dirReached, ArrayList fileReached, int numOfDir) { + String errMsg = String.format("File/Directory name is not as expected name: %s", file.getName()); + if (file.isDir() && !file.getName().equals(".") && !file.getName().equals("..")) { + numOfDir++; + assertEquals(errMsg, String.format("dir%d", numOfDir), file.getName()); + dirReached.append(file.getName()).append("/"); + try { + List children = file.listFiles(); + for (AbstractFile child : children) { + checkEachFileInDeepFolder(child, dirReached, fileReached, numOfDir); + } + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } else if (file.isFile() && !file.getName().endsWith("slack")) { + assertEquals(errMsg, "file.txt", file.getName()); + fileReached.add(file.getParentPath() + file.getName()); + } + } + + private boolean checkFileInEmbeddedFolder(AbstractFile file) { + if (file.getName().equals("level1.txt")) { + return true; + } else if (file.getNameExtension().equalsIgnoreCase("zip")) { + try { + List children = file.listFiles(); + for (AbstractFile child : children) { + return checkFileInEmbeddedFolder(child); + } + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } else { + assertTrue(file.getNameExtension().equalsIgnoreCase("txt")); + } + + return false; + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index e9cab64a20..810c682798 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -18,28 +18,22 @@ */ package org.sleuthkit.autopsy.ingest; -import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import static junit.framework.Assert.assertFalse; import org.netbeans.junit.NbModuleSuite; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.casemodule.CaseDetails; import junit.framework.Test; -import org.apache.commons.io.FileUtils; import org.netbeans.junit.NbTestCase; import org.openide.util.Exceptions; import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor; import org.sleuthkit.autopsy.casemodule.services.FileManager; -import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule; @@ -48,19 +42,18 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.FullNameCond import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCondition; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition; import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory; -import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner; -import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback; +import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.IngestJobRunner; +import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; public class IngestFileFiltersTest extends NbTestCase { - private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "IngestFileFiltersTest"); - private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString()); private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"filter_test1.img"); + private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "local_files_test.zip"); public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestFileFiltersTest.class). @@ -73,74 +66,29 @@ public class IngestFileFiltersTest extends NbTestCase { super(name); } - @Override - public void setUp() { - // Delete the test directory, if it exists - if (CASE_DIRECTORY_PATH.toFile().exists()) { - try { - FileUtils.deleteDirectory(CASE_DIRECTORY_PATH.toFile()); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - assertFalse("Unable to delete existing test directory", CASE_DIRECTORY_PATH.toFile().exists()); - - // Create the test directory - CASE_DIRECTORY_PATH.toFile().mkdirs(); - assertTrue("Unable to create test directory", CASE_DIRECTORY_PATH.toFile().exists()); - - try { - Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, CASE_DIRECTORY_PATH.toString(), new CaseDetails("IngestFiltersTest")); - } catch (CaseActionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - assertTrue(CASE_DIR.exists()); - ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - try { - ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, IMAGE_PATH); - List dataSourceContent = callBack.getDataSourceContent(); - assertEquals(1, dataSourceContent.size()); - List errorMessages = callBack.getErrorMessages(); - assertEquals(0, errorMessages.size()); - } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - - } - } - @Override public void tearDown() { - try { - Case.closeCurrentCase(); - //Seems like we need some time to close the case. - try { - Thread.sleep(2000); - } catch (Exception ex) { - - } - - FileUtils.deleteDirectory(CASE_DIR); - } catch (CaseActionException | IOException ex) { - Exceptions.printStackTrace(ex); - } - assertFalse(CASE_DIR.exists()); + CaseUtils.closeCase(); } public void testBasicDir() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir"); + CaseUtils.createCase(casePath, "testBasicDir"); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + HashMap rule = new HashMap<>(); rule.put("Rule", new Rule("testFileType", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); //Filter for dir1 and no unallocated space FilesSet dirFilter = new FilesSet("Filter", "Filter to find all files in dir1.", false, true, rule); try { - Case openCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCase(); ArrayList templates = new ArrayList<>(); - templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); - runIngestJob(openCase.getDataSources(), templates, dirFilter); - FileManager fileManager = openCase.getServices().getFileManager(); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, dirFilter); + IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings); + FileManager fileManager = currentCase.getServices().getFileManager(); List results = fileManager.findFiles("file.jpg", "dir1"); String mimeType = results.get(0).getMIMEType(); assertEquals("image/jpeg", mimeType); @@ -159,24 +107,31 @@ public class IngestFileFiltersTest extends NbTestCase { assertTrue(errMsg, file.getMIMEType() == null); } } - } catch (NoCurrentCaseException | TskCoreException ex) { + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } + } public void testExtAndDirWithOneRule() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule"); + CaseUtils.createCase(casePath, "testExtAndDirWithOneRule"); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + HashMap rules = new HashMap<>(); rules.put("Rule", new Rule("testExtAndDirWithOneRule", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); //Build the filter that ignore unallocated space and with one rule FilesSet filesExtDirsFilter = new FilesSet("Filter", "Filter to find all jpg files in dir1.", false, true, rules); try { - Case openCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCase(); ArrayList templates = new ArrayList<>(); - templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); - runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter); - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter); + IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings); + FileManager fileManager = currentCase.getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(62, results.size()); for (AbstractFile file : results) { @@ -189,13 +144,18 @@ public class IngestFileFiltersTest extends NbTestCase { assertTrue(errMsg, file.getMIMEType() == null); } } - } catch (NoCurrentCaseException | TskCoreException ex) { + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } public void testExtAndDirWithTwoRules() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules"); + CaseUtils.createCase(casePath, "testExtAndDirWithTwoRules"); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + HashMap rules = new HashMap<>(); rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); rules.put("rule2", new Rule("FindDir1Directory", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); @@ -203,11 +163,12 @@ public class IngestFileFiltersTest extends NbTestCase { FilesSet filesExtDirsFilter = new FilesSet("Filter", "Filter to find all files in dir1 and all files with jpg extention.", false, true, rules); try { - Case openCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCase(); ArrayList templates = new ArrayList<>(); - templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); - runIngestJob(openCase.getDataSources(), templates, filesExtDirsFilter); - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter); + IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings); + FileManager fileManager = currentCase.getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(62, results.size()); for (AbstractFile file : results) { @@ -228,25 +189,30 @@ public class IngestFileFiltersTest extends NbTestCase { assertTrue(errMsg, file.getMIMEType() == null); } } - } catch (NoCurrentCaseException | TskCoreException ex) { + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } public void testFullFileNameRule() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule"); + CaseUtils.createCase(casePath, "testFullFileNameRule"); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + HashMap rules = new HashMap<>(); rules.put("rule", new Rule("FindFileWithFullName", new FullNameCondition("file.docx"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); //Build the filter to find file: file.docx FilesSet fullNameFilter = new FilesSet("Filter", "Filter to find file.docx.", false, true, rules); try { - Case openCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCase(); ArrayList templates = new ArrayList<>(); - templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); - - runIngestJob(openCase.getDataSources(), templates, fullNameFilter); - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, fullNameFilter); + IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings); + FileManager fileManager = currentCase.getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(62, results.size()); for (AbstractFile file : results) { @@ -259,13 +225,18 @@ public class IngestFileFiltersTest extends NbTestCase { assertTrue(errMsg, file.getMIMEType() == null); } } - } catch (NoCurrentCaseException | TskCoreException ex) { + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } public void testCarvingWithExtRuleAndUnallocSpace() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace"); + CaseUtils.createCase(casePath, "testCarvingWithExtRuleAndUnallocSpace"); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + HashMap rules = new HashMap<>(); rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); rules.put("rule2", new Rule("FindGifExtention", new ExtensionCondition("gif"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); @@ -274,12 +245,13 @@ public class IngestFileFiltersTest extends NbTestCase { FilesSet extensionFilter = new FilesSet("Filter", "Filter to files with .jpg and .gif extension.", false, false, rules); try { - Case openCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCase(); ArrayList templates = new ArrayList<>(); - templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); - templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); - runIngestJob(openCase.getDataSources(), templates, extensionFilter); - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, extensionFilter); + IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings); + FileManager fileManager = currentCase.getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(70, results.size()); int carvedJpgGifFiles = 0; @@ -302,13 +274,18 @@ public class IngestFileFiltersTest extends NbTestCase { //Make sure we have carved jpg/gif files assertEquals(2, carvedJpgGifFiles); - } catch (NoCurrentCaseException | TskCoreException ex) { + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } public void testCarvingNoUnallocatedSpace() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace"); + CaseUtils.createCase(casePath, "testCarvingNoUnallocatedSpace"); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + HashMap rules = new HashMap<>(); rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); rules.put("rule2", new Rule("FindGifExtention", new ExtensionCondition("gif"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); @@ -317,13 +294,13 @@ public class IngestFileFiltersTest extends NbTestCase { FilesSet extensionFilter = new FilesSet("Filter", "Filter to files with .jpg and .gif extension.", false, true, rules); try { - Case openCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCase(); ArrayList templates = new ArrayList<>(); - templates.add(getIngestModuleTemplate(new FileTypeIdModuleFactory())); - templates.add(getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, extensionFilter); try { - List errs = IngestJobRunner.runIngestJob(openCase.getDataSources(), ingestJobSettings); + List errs = IngestJobRunner.runIngestJob(currentCase.getDataSources(), ingestJobSettings); //Ingest fails because Carving wants unallocated space assertEquals(1, errs.size()); assertEquals("PhotoRec Carver", errs.get(0).getModuleDisplayName()); @@ -331,30 +308,56 @@ public class IngestFileFiltersTest extends NbTestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - } catch (NoCurrentCaseException | TskCoreException ex) { + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } - private void runIngestJob(List datasources, ArrayList templates, FilesSet filter) { - IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, filter); + public void testEmbeddedModule() { + Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule"); + CaseUtils.createCase(casePath, "testEmbeddedModule"); + LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, ZIPFILE_PATH); + + //Build the filter to find jpg files + HashMap rules = new HashMap<>(); + //Extension condition for jpg files + rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); + //Extension condition for zip files, because we want test jpg extension filter for extracted files from a zip file + rules.put("rule2", new Rule("ZipExtention", new ExtensionCondition("zip"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); + FilesSet embeddedFilter = new FilesSet("Filter", "Filter to files with .jpg extension.", false, false, rules); + try { - List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); - for (IngestModuleError err : errs) { - System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); + Case currentCase = Case.getCurrentCase(); + ArrayList templates = new ArrayList<>(); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, embeddedFilter); + IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings); + FileManager fileManager = currentCase.getServices().getFileManager(); + //get all .jpg files in zip file + List results = fileManager.findFiles("%%"); + assertEquals(39, results.size()); + int numTypeJpgFiles = 0; + for (AbstractFile file : results) { + if (file.getNameExtension().equalsIgnoreCase("jpg") || file.getNameExtension().equalsIgnoreCase("zip")) { + String errMsg = String.format("File %s (objId=%d) unexpectedly blocked by the file filter.", file.getName(), file.getId()); + assertTrue(errMsg, file.getMIMEType() != null && !file.getMIMEType().isEmpty()); + numTypeJpgFiles++; + } else if (file.isDir() && (file.getType() == TSK_DB_FILES_TYPE_ENUM.DERIVED || file.getType() == TSK_DB_FILES_TYPE_ENUM.LOCAL)) { + String errMsg = String.format("File %s (objId=%d) unexpectedly blocked by the file filter.", file.getName(), file.getId()); + assertTrue(errMsg, file.getMIMEType() != null && !file.getMIMEType().isEmpty()); + } else { + String errMsg = String.format("File %s (objId=%d) unexpectedly passed by the file filter.", file.getName(), file.getId()); + assertTrue(errMsg, file.getMIMEType() == null); + } } - assertEquals(0, errs.size()); - } catch (InterruptedException ex) { + //Make sure 10 jpg files and 1 zip file have been typed + assertEquals(11, numTypeJpgFiles); + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } + } } - - private IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { - IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); - IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); - template.setEnabled(true); - return template; - } - } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java new file mode 100755 index 0000000000..b1963c3fd0 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -0,0 +1,161 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.netbeans.junit.NbModuleSuite; +import org.sleuthkit.autopsy.casemodule.Case; +import junit.framework.Test; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.autopsy.testutils.CaseUtils; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +public class EncryptionDetectionTest extends NbTestCase { + + private static final String CASE_NAME = "EncryptionDetectionTest"; + private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EncryptionDetectionTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + public EncryptionDetectionTest(String name) { + super(name); + } + + @Override + public void setUp() { + CaseUtils.createCase(CASE_DIRECTORY_PATH, CASE_NAME); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); + } + + @Override + public void tearDown() { + CaseUtils.closeCase(); + } + + /** + * Test the Encryption Detection module's password protection detection. + */ + public void testPasswordProtection() { + try { + Case openCase = Case.getOpenCase(); + + /* + * Create ingest job settings. + */ + IngestModuleFactory ingestModuleFactory = new EncryptionDetectionModuleFactory(); + IngestModuleIngestJobSettings settings = ingestModuleFactory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(ingestModuleFactory, settings); + template.setEnabled(true); + List templates = new ArrayList<>(); + templates.add(template); + IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + + /* + * Purge specific files to be tested. + */ + FileManager fileManager = openCase.getServices().getFileManager(); + List> allResults = new ArrayList<>(0); + + List ole2Results = fileManager.findFiles("%%", "ole2"); + assertEquals("Unexpected number of OLE2 results.", 11, ole2Results.size()); + + List ooxmlResults = fileManager.findFiles("%%", "ooxml"); + assertEquals("Unexpected number of OOXML results.", 13, ooxmlResults.size()); + + List pdfResults = fileManager.findFiles("%%", "pdf"); + assertEquals("Unexpected number of PDF results.", 6, pdfResults.size()); + + List mdbResults = fileManager.findFiles("%%", "mdb"); + assertEquals("Unexpected number of MDB results.", 25, mdbResults.size()); + + List accdbResults = fileManager.findFiles("%%", "accdb"); + assertEquals("Unexpected number of ACCDB results.", 10, accdbResults.size()); + + allResults.add(ole2Results); + allResults.add(ooxmlResults); + allResults.add(pdfResults); + allResults.add(mdbResults); + allResults.add(accdbResults); + + for (List results : allResults) { + for (AbstractFile file : results) { + /* + * Process only non-slack files. + */ + if (file.isFile() && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) { + /* + * Determine which assertions to use for the file based on + * its name. + */ + boolean fileProtected = file.getName().split("\\.")[0].endsWith("-protected"); + List artifactsList = file.getAllArtifacts(); + if (fileProtected) { + /* + * Check that the protected file has one + * TSK_ENCRYPTION_DETECTED artifact. + */ + int artifactsListSize = artifactsList.size(); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but 1 was expected.", file.getName(), file.getId(), artifactsListSize); + assertEquals(errorMessage, 1, artifactsListSize); + + String artifactTypeName = artifactsList.get(0).getArtifactTypeName(); + errorMessage = String.format("File '%s' (objId=%d) has an unexpected '%s' artifact.", file.getName(), file.getId(), artifactTypeName); + assertEquals(errorMessage, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.toString(), artifactTypeName); + } else { + /* + * Check that the unprotected file has no artifacts. + */ + int artifactsListSize = artifactsList.size(); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but none were expected.", file.getName(), file.getId(), artifactsListSize); + assertEquals(errorMessage, 0, artifactsListSize); + } + } + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java new file mode 100755 index 0000000000..5eecf425bd --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java @@ -0,0 +1,104 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.testutils; + +import java.io.IOException; +import java.nio.file.Path; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import org.apache.commons.io.FileUtils; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; + +/** + * Common case utility methods. + */ +public final class CaseUtils { + + /** + * CaseUtils constructor. Since this class is not meant to allow for + * instantiation, this constructor is 'private'. + */ + private CaseUtils() { + } + + /** + * Create a new case. If the case already exists at the specified path, the + * existing case will be removed prior to creation of the new case. + * + * @param caseDirectoryPath The path to the case data. + * @param caseDisplayName The display name for the case. + */ + public static void createCase(Path caseDirectoryPath, String caseDisplayName) { + //Make sure the test is starting with a clean state. So delete the test directory, if it exists. + deleteCaseDir(caseDirectoryPath); + assertFalse("Unable to delete existing test directory", caseDirectoryPath.toFile().exists()); + + // Create the test directory + caseDirectoryPath.toFile().mkdirs(); + assertTrue("Unable to create test directory", caseDirectoryPath.toFile().exists()); + + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), new CaseDetails(caseDisplayName)); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + assertTrue(caseDirectoryPath.toFile().exists()); + } + + /** + * Close the currently opened case. + */ + public static void closeCase() { + try { + Case.closeCurrentCase(); + //Seems like we need some time to close the case, so file handler later can delete the case directory. + try { + Thread.sleep(20000); + } catch (Exception ex) { + + } + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Delete a case at the specified path. + * + * @param caseDirectoryPath The path to the case to be removed. + */ + public static void deleteCaseDir(Path caseDirectoryPath) { + if (!caseDirectoryPath.toFile().exists()) { + return; + } + try { + FileUtils.deleteDirectory(caseDirectoryPath.toFile()); + } catch (IOException ex) { + //We just want to make sure the case directory doesn't exist when the test starts. It shouldn't cause failure if the case directory couldn't be deleted after a test finished. + System.out.println("INFO: Unable to delete case directory: " + caseDirectoryPath.toString()); + } + } + +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java new file mode 100755 index 0000000000..fba62cdbde --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.testutils; + +import java.nio.file.Path; +import java.util.List; +import static junit.framework.Assert.assertEquals; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.datamodel.Content; + +/** + * Common image utility methods. + */ +public final class IngestUtils { + + /** + * IngestUtils constructor. Since this class is not meant to allow for + * instantiation, this constructor is 'private'. + */ + private IngestUtils() { + } + + /** + * Add a data source for the data source processor. + * + * @param dataSourceProcessor The data source processor. + * @param dataSourcePath The path to the data source to be added. + */ + public static void addDataSource(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) { + try { + DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath); + List callbackErrorMessageList = callBack.getErrorMessages(); + String errorMessage = String.format("The data source processor callback produced %d error messages.", callbackErrorMessageList.size()); + assertEquals(errorMessage, 0, callbackErrorMessageList.size()); + } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + + } + } + + /** + * Run an ingest job. + * + * @param dataSourceList The list of data sources to process. + * @param ingestJobSettings The ingest job settings to use for ingest. + */ + public static void runIngestJob(List dataSourceList, IngestJobSettings ingestJobSettings) { + try { + List ingestModuleErrorsList = IngestJobRunner.runIngestJob(dataSourceList, ingestJobSettings); + for (IngestModuleError err : ingestModuleErrorsList) { + System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); + } + String errorMessage = String.format("The ingest job runner produced %d error messages.", ingestModuleErrorsList.size()); + assertEquals(errorMessage, 0, ingestModuleErrorsList.size()); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Build a new ingest module template based on the given factory. + * + * @param factory The ingest module factory. + * + * @return The ingest module template. + */ + public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { + IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); + template.setEnabled(true); + return template; + } + +} diff --git a/CoreLibs/build.xml b/CoreLibs/build.xml index 4084652c2c..f4c8cf0844 100644 --- a/CoreLibs/build.xml +++ b/CoreLibs/build.xml @@ -23,7 +23,7 @@ - + diff --git a/Experimental/build.xml b/Experimental/build.xml index b3923a5c30..3264616a39 100644 --- a/Experimental/build.xml +++ b/Experimental/build.xml @@ -13,7 +13,7 @@ - + diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index dbd5a31c4c..6d3d3f6730 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -33,6 +33,14 @@ 1.49.1 + + org.netbeans.swing.outline + + + + 1.34.1 + + org.openide.awt @@ -49,6 +57,14 @@ 7.41.1 + + org.openide.explorer + + + + 6.62.1 + + org.openide.filesystems @@ -119,7 +135,7 @@ 10 - 10.10 + 10.11 @@ -137,7 +153,7 @@ 6 - 6.3 + 6.5 diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java index 3baf91b871..9830cecbe9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java @@ -100,7 +100,7 @@ class AddArchiveTask implements Runnable { // extract the archive and pass the extracted folder as input try { - Case currentCase = Case.getOpenCase(); + Case currentCase = Case.getCurrentCaseThrows(); // create folder to extract archive to Path destinationFolder = createDirectoryForFile(archivePath, currentCase.getModuleDirectory()); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.form new file mode 100644 index 0000000000..9f0ddc84c8 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.form @@ -0,0 +1,93 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java new file mode 100644 index 0000000000..b68553039d --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java @@ -0,0 +1,151 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.Cursor; +import java.awt.EventQueue; +import java.util.Observable; +import java.util.Observer; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState; + +/** + * A dashboard for monitoring the existing AutoIngestNodes and their status. + */ +final class AinStatusDashboard extends javax.swing.JPanel implements Observer { + + private final AutoIngestMonitor autoIngestMonitor; + private final AinStatusPanel nodesPanel; + + /** + * Creates new form AutoIngestNodeStatus + */ + AinStatusDashboard(AutoIngestMonitor monitor) { + initComponents(); + autoIngestMonitor = monitor; + nodesPanel = new AinStatusPanel(); + nodesPanel.setSize(nodesPanel.getSize()); + nodeStatusScrollPane.add(nodesPanel); + nodeStatusScrollPane.setViewportView(nodesPanel); + refreshTables(); + } + + /** + * Adds this panel as an observer of AutoIngestMonitor. + */ + void startUp() { + autoIngestMonitor.addObserver(this); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + refreshButton = new javax.swing.JButton(); + clusterMetricsButton = new javax.swing.JButton(); + nodeStatusScrollPane = new javax.swing.JScrollPane(); + nodeStatusTableTitle = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.refreshButton.text")); // NOI18N + refreshButton.setToolTipText(org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.refreshButton.toolTipText")); // NOI18N + refreshButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + refreshButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.clusterMetricsButton.text")); // NOI18N + clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + clusterMetricsButtonActionPerformed(evt); + } + }); + + nodeStatusTableTitle.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(nodeStatusTableTitle, org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.nodeStatusTableTitle.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(nodeStatusScrollPane) + .addGroup(layout.createSequentialGroup() + .addComponent(nodeStatusTableTitle) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 715, Short.MAX_VALUE) + .addComponent(clusterMetricsButton))) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, refreshButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(40, 40, 40) + .addComponent(nodeStatusTableTitle, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(nodeStatusScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(382, 382, 382) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(refreshButton) + .addComponent(clusterMetricsButton)) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + refreshTables(); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }//GEN-LAST:event_refreshButtonActionPerformed + + private void refreshTables() { + nodesPanel.refresh(autoIngestMonitor); + } + + private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed + new AutoIngestMetricsDialog(this.getTopLevelAncestor()); + }//GEN-LAST:event_clusterMetricsButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton clusterMetricsButton; + private javax.swing.JScrollPane nodeStatusScrollPane; + private javax.swing.JLabel nodeStatusTableTitle; + private javax.swing.JButton refreshButton; + // End of variables declaration//GEN-END:variables + + @Override + public void update(Observable o, Object arg) { + if (arg instanceof AutoIngestNodeState) + EventQueue.invokeLater(() -> { + refreshTables(); + }); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.form new file mode 100644 index 0000000000..5f3eab1a5f --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.form @@ -0,0 +1,28 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java new file mode 100644 index 0000000000..1ab19b4a77 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java @@ -0,0 +1,140 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.util.List; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.Mode; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Top component which displays the Auto Ingest Node Status Dashboard interface. + */ +@TopComponent.Description( + preferredID = "AinStatusDashboardTopComponent", + persistenceType = TopComponent.PERSISTENCE_NEVER +) +@TopComponent.Registration(mode = "nodeStatus", openAtStartup = false) +@Messages({ + "CTL_AinStatusDashboardAction=Auto Ingest Nodes", + "CTL_AinStatusDashboardTopComponent=Auto Ingest Nodes"}) +final class AinStatusDashboardTopComponent extends TopComponent { + + private static final long serialVersionUID = 1L; + public final static String PREFERRED_ID = "AinStatusDashboardTopComponent"; // NON-NLS + private static final Logger logger = Logger.getLogger(AinStatusDashboardTopComponent.class.getName()); + private static boolean topComponentInitialized = false; + + @Messages({ + "AinStatusDashboardTopComponent.exceptionMessage.failedToCreateDashboard=Failed to create Auto Ingest Node Status Dashboard.",}) + static void openTopComponent(AutoIngestMonitor monitor) { + final AinStatusDashboardTopComponent tc = (AinStatusDashboardTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + if (tc != null) { + topComponentInitialized = true; + WindowManager.getDefault().isTopComponentFloating(tc); + + if (tc.isOpened() == false) { + Mode mode = WindowManager.getDefault().findMode("nodeStatus"); // NON-NLS + if (mode != null) { + mode.dockInto(tc); + } + /* + * Make sure we have a clean-slate before attaching a new + * dashboard instance so we don't accumulate them. + */ + tc.removeAll(); + + /* + * Create a new dashboard instance to ensure we're using the + * most recent configuration. + */ + AinStatusDashboard nodeTab = new AinStatusDashboard(monitor); + nodeTab.startUp(); + nodeTab.setSize(nodeTab.getPreferredSize()); + tc.add(nodeTab); + tc.open(); + } + tc.toFront(); + tc.requestActive(); + } + } + + static void closeTopComponent() { + if (topComponentInitialized) { + final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + if (tc != null) { + try { + tc.close(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS + } + } + } + } + + AinStatusDashboardTopComponent() { + initComponents(); + setName(Bundle.CTL_AinStatusDashboardTopComponent()); + } + + + @Override + public List availableModes(List modes) { + /* + * This looks like the right thing to do, but online discussions seems + * to indicate this method is effectively deprecated. A break point + * placed here was never hit. + */ + return modes.stream().filter(mode -> mode.getName().equals("nodeStatus") || mode.getName().equals("ImageGallery")) + .collect(Collectors.toList()); + } + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java new file mode 100644 index 0000000000..444a6fce8b --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java @@ -0,0 +1,156 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import javax.swing.Action; +import java.util.ArrayList; +import java.util.List; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState; + +/** + * A node which represents all AutoIngestNodes. Each AutoIngestNode will have a + * child node representing it and its status. + */ +final class AinStatusNode extends AbstractNode { + + /** + * Construct a new AinStatusNode. + */ + AinStatusNode(AutoIngestMonitor monitor) { + super(Children.create(new AinStatusChildren(monitor), false)); + } + + /** + * A ChildFactory for generating StatusNodes. + */ + static class AinStatusChildren extends ChildFactory { + + private final AutoIngestMonitor monitor; + + /** + * Create children nodes for the AutoIngestNodeState which will each + * represent a single node state + * + * @param autoIngestMonitor the monitor which contains the node states + */ + AinStatusChildren(AutoIngestMonitor autoIngestMonitor) { + monitor = autoIngestMonitor; + } + + @Override + protected boolean createKeys(List list) { + list.addAll(monitor.getNodeStates()); + return true; + } + + @Override + protected Node createNodeForKey(AutoIngestNodeState key) { + return new StatusNode(key); + } + } + + /** + * A node which represents a single AutoIngestNode and its status. + */ + static final class StatusNode extends AbstractNode { + + private final AutoIngestNodeState nodeState; + + /** + * Construct a new StatusNode to represent an AutoIngestNode and its + * status. + * + * @param nodeState - the AutoIngestNodeState being represented by this + * node + */ + StatusNode(AutoIngestNodeState nodeState) { + super(Children.LEAF); + this.nodeState = nodeState; + setName(nodeState.getName()); + setDisplayName(nodeState.getName()); + } + + @Override + @Messages({"AinStatusNode.hostName.title=Host Name", + "AinStatusNode.status.title=Status", + "AinStatusNode.status.running=Running", + "AinStatusNode.status.pausedByUser=Paused By User", + "AinStatusNode.status.pausedForError=Paused Due to System Error", + "AinStatusNode.status.startingup=Starting Up", + "AinStatusNode.status.shuttingdown=Shutting Down", + "AinStatusNode.status.unknown=Unknown" + }) + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + ss.put(new NodeProperty<>(Bundle.AinStatusNode_hostName_title(), Bundle.AinStatusNode_hostName_title(), Bundle.AinStatusNode_hostName_title(), + nodeState.getName())); + String status = Bundle.AinStatusNode_status_unknown(); + switch (nodeState.getState()) { + case RUNNING: + status = Bundle.AinStatusNode_status_running(); + break; + case STARTING_UP: + status = Bundle.AinStatusNode_status_startingup(); + break; + case SHUTTING_DOWN: + status = Bundle.AinStatusNode_status_shuttingdown(); + break; + case PAUSED_BY_REQUEST: + status = Bundle.AinStatusNode_status_pausedByUser(); + break; + case PAUSED_DUE_TO_SYSTEM_ERROR: + status = Bundle.AinStatusNode_status_pausedForError(); + break; + default: + break; + } + ss.put(new NodeProperty<>(Bundle.AinStatusNode_status_title(), Bundle.AinStatusNode_status_title(), Bundle.AinStatusNode_status_title(), + status)); + return s; + } + + @Override + public Action[] getActions(boolean context) { + List actions = new ArrayList<>(); + if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { + if (nodeState.getState() == AutoIngestNodeState.State.PAUSED_BY_REQUEST + || nodeState.getState() == AutoIngestNodeState.State.PAUSED_DUE_TO_SYSTEM_ERROR) { + actions.add(new AutoIngestAdminActions.ResumeAction()); + } else if (nodeState.getState() == AutoIngestNodeState.State.RUNNING){ + actions.add(new AutoIngestAdminActions.PauseAction()); + } + actions.add(new AutoIngestAdminActions.ShutdownAction()); + } + return actions.toArray(new Action[actions.size()]); + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.form new file mode 100644 index 0000000000..bde4db4324 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.form @@ -0,0 +1,18 @@ + + +
+ + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java new file mode 100644 index 0000000000..095badff9a --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java @@ -0,0 +1,142 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.Dimension; +import java.beans.PropertyVetoException; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionListener; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; + +/** + * A panel which displays an outline view with all auto ingest nodes and their + * status. + */ +final class AinStatusPanel extends javax.swing.JPanel implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + private final org.openide.explorer.view.OutlineView outlineView; + private final Outline outline; + private ExplorerManager explorerManager; + + /** + * Creates a new AinStatusPanel + */ + AinStatusPanel() { + initComponents(); + outlineView = new org.openide.explorer.view.OutlineView(); + outline = outlineView.getOutline(); + customize(); + } + + /** + * Set up the AinStatusPanel's so that its outlineView is displaying the + * host name and the node status. + */ + void customize() { + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AinStatusNode_hostName_title()); + outline.setRowSelectionAllowed(false); //rows will be made selectable after table has been populated + outline.setFocusable(false); //table will be made focusable after table has been populated + if (null == explorerManager) { + explorerManager = new ExplorerManager(); + } + outlineView.setPropertyColumns( + Bundle.AinStatusNode_status_title(), Bundle.AinStatusNode_status_title()); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + outline.setRootVisible(false); + add(outlineView, java.awt.BorderLayout.CENTER); + } + + @Override + public void setSize(Dimension d) { + super.setSize(d); + outlineView.setMaximumSize(new Dimension(400, 100)); + outline.setPreferredScrollableViewportSize(new Dimension(400, 100)); + } + + /** + * Add a list selection listener to the selection model of the outline being + * used in this panel. + * + * @param listener the ListSelectionListener to add + */ + void addListSelectionListener(ListSelectionListener listener) { + outline.getSelectionModel().addListSelectionListener(listener); + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + /** + * Update the contents of this AinStatusPanel while retaining currently + * selected node. + * + * @param monitor - the AutoIngestMonitor which will provide the new + * contents + */ + void refresh(AutoIngestMonitor monitor) { + outline.setRowSelectionAllowed(false); + Node[] selectedNodes = explorerManager.getSelectedNodes(); + AinStatusNode autoIngestNode = new AinStatusNode(monitor); + explorerManager.setRootContext(autoIngestNode); + outline.setRowSelectionAllowed(true); + if (selectedNodes.length > 0 && autoIngestNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored + try { + explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); + } catch (PropertyVetoException ignore) { + //Unable to select previously selected node + } + } + outline.setFocusable(true); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new java.awt.BorderLayout()); + }// //GEN-END:initComponents + + /** + * Get the AutoIngestJob for the currently selected node of this panel. + * + * @return AutoIngestJob which is currently selected in this panel + */ + AutoIngestJob getSelectedAutoIngestJob() { + Node[] selectedRows = explorerManager.getSelectedNodes(); + if (selectedRows.length == 1) { + return ((JobNode) selectedRows[0]).getAutoIngestJob(); + } + return null; + } + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java index 449c36dfff..e0a5378444 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java @@ -217,7 +217,7 @@ class ArchiveFilePanel extends JPanel implements DocumentListener { // display warning if there is one (but don't disable "next" button) try { - if (false == PathValidator.isValid(path, Case.getOpenCase().getCaseType())) { + if (false == PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.DataSourceOnCDriveError_text()); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java new file mode 100644 index 0000000000..1a5f2657b6 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -0,0 +1,216 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; + +final class AutoIngestAdminActions { + + @NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"}) + static final class ProgressDialogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ProgressDialogAction() { + super(Bundle.AutoIngestAdminActions_progressDialogAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3734 + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc != null) { + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true); + } + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.cancelJobAction.title=Cancel Job"}) + static final class CancelJobAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + CancelJobAction() { + super(Bundle.AutoIngestAdminActions_cancelJobAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3738 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.cancelModuleAction.title=Cancel Module"}) + static final class CancelModuleAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + CancelModuleAction() { + super(Bundle.AutoIngestAdminActions_cancelModuleAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3738 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.reprocessJobAction.title=Reprocess Job"}) + static final class ReprocessJobAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ReprocessJobAction() { + super(Bundle.AutoIngestAdminActions_reprocessJobAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3739 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case"}) + static final class DeleteCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + DeleteCaseAction() { + super(Bundle.AutoIngestAdminActions_deleteCaseAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3740 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log"}) + static final class ShowCaseLogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ShowCaseLogAction() { + super(Bundle.AutoIngestAdminActions_showCaseLogAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA- + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.pause.title=Pause Node"}) + static final class PauseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + PauseAction() { + super(Bundle.AutoIngestAdminActions_pause_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA- + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.resume.title=Resume Node"}) + static final class ResumeAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ResumeAction() { + super(Bundle.AutoIngestAdminActions_resume_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA- + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.shutdown.title=Shutdown Node"}) + static final class ShutdownAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ShutdownAction() { + super(Bundle.AutoIngestAdminActions_shutdown_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA- + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 9d2a343f56..214254e032 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -26,37 +26,29 @@ - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + @@ -65,60 +57,48 @@ - + - + - - + + - - + + - - - + + + - - - - - +
+ + + + + + + + - - - - - - - - - - - - - - - - @@ -126,22 +106,6 @@ - - - - - - - - - - - - - - - - @@ -149,22 +113,6 @@ - - - - - - - - - - - - - - - - @@ -233,34 +181,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -271,33 +191,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 3389a64a03..3704bf7a7e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -19,74 +19,41 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Cursor; -import java.awt.EventQueue; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Instant; -import java.util.Date; -import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.logging.Level; -import javax.swing.DefaultListSelectionModel; import java.awt.Color; +import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.swing.JPanel; -import javax.swing.JTable; import javax.swing.SwingWorker; import javax.swing.UIManager; -import javax.swing.event.ListSelectionEvent; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumn; +import org.openide.modules.Places; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; -import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; -import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer; -import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; /** * A dashboard for monitoring an automated ingest cluster. */ final class AutoIngestDashboard extends JPanel implements Observer { - - private static final long serialVersionUID = 1L; - private static final int GENERIC_COL_MIN_WIDTH = 30; - private static final int GENERIC_COL_MAX_WIDTH = 2000; - private static final int PENDING_TABLE_COL_PREFERRED_WIDTH = 280; - private static final int RUNNING_TABLE_COL_PREFERRED_WIDTH = 175; - private static final int PRIORITY_COLUMN_PREFERRED_WIDTH = 60; - private static final int PRIORITY_COLUMN_MAX_WIDTH = 150; - private static final int STAGE_TIME_COL_MIN_WIDTH = 250; - private static final int STAGE_TIME_COL_MAX_WIDTH = 450; - private static final int TIME_COL_MIN_WIDTH = 30; - private static final int TIME_COL_MAX_WIDTH = 250; - private static final int TIME_COL_PREFERRED_WIDTH = 140; - private static final int NAME_COL_MIN_WIDTH = 100; - private static final int NAME_COL_MAX_WIDTH = 250; - private static final int NAME_COL_PREFERRED_WIDTH = 140; - private static final int STAGE_COL_MIN_WIDTH = 70; - private static final int STAGE_COL_MAX_WIDTH = 2000; - private static final int STAGE_COL_PREFERRED_WIDTH = 300; - private static final int STATUS_COL_MIN_WIDTH = 55; - private static final int STATUS_COL_MAX_WIDTH = 250; - private static final int STATUS_COL_PREFERRED_WIDTH = 55; - private static final int COMPLETED_TIME_COL_MIN_WIDTH = 30; - private static final int COMPLETED_TIME_COL_MAX_WIDTH = 2000; - private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; - private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName()); - private final DefaultTableModel pendingTableModel; - private final DefaultTableModel runningTableModel; - private final DefaultTableModel completedTableModel; - private AutoIngestMonitor autoIngestMonitor; + private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; + private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME; + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName()); + private AutoIngestMonitor autoIngestMonitor; + private AutoIngestJobsPanel pendingJobsPanel; + private AutoIngestJobsPanel runningJobsPanel; + private AutoIngestJobsPanel completedJobsPanel; + /** * Maintain a mapping of each service to it's last status update. */ @@ -113,38 +80,56 @@ final class AutoIngestDashboard extends JPanel implements Observer { /** * Constructs a panel for monitoring an automated ingest cluster. */ + @Messages({"AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first", + "AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it", + "AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already"}) + private AutoIngestDashboard() { this.statusByService = new ConcurrentHashMap<>(); - - pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - - runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - - completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); - initPendingJobsTable(); - initRunningJobsTable(); - initCompletedJobsTable(); - + pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.PENDING_JOB); + pendingJobsPanel.setSize(pendingScrollPane.getSize()); + pendingScrollPane.add(pendingJobsPanel); + pendingScrollPane.setViewportView(pendingJobsPanel); + pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable_toolTipText()); + runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.RUNNING_JOB); + runningJobsPanel.setSize(runningScrollPane.getSize()); + runningScrollPane.add(runningJobsPanel); + runningScrollPane.setViewportView(runningJobsPanel); + runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable_toolTipText()); + completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.COMPLETED_JOB); + completedJobsPanel.setSize(completedScrollPane.getSize()); + completedScrollPane.add(completedJobsPanel); + completedScrollPane.setViewportView(completedJobsPanel); + completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable_toolTipText()); /* * Must set this flag, otherwise pop up menus don't close properly. */ + UIManager.put("PopupMenu.consumeEventOnClose", false); } - + + AutoIngestMonitor getMonitor() { + return autoIngestMonitor; + } + + AutoIngestJobsPanel getPendingJobsPanel() { + return pendingJobsPanel; + } + /** * Update status of the services on the dashboard */ private void displayServicesStatus() { - tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", - statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), - statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), - statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", + statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), statusByService.get(ServicesMonitor.Service.MESSAGING.toString()))); String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0 @@ -162,7 +147,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ private void setServicesStatusMessage() { new SwingWorker() { - + @Override protected Void doInBackground() throws Exception { statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE)); @@ -202,233 +187,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { }.execute(); } - /** - * Sets up the JTable that presents a view of the pending jobs queue for an - * auto ingest cluster. - */ - private void initPendingJobsTable() { - /* - * Remove some of the jobs table model columns from the JTable. This - * does not remove the columns from the model, just from this table. - */ - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - - /* - * Set up a column to display the cases associated with the jobs. - */ - TableColumn column; - column = pendingTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the data sources associated with the jobs. - */ - column = pendingTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the create times of the jobs. - */ - column = pendingTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); - column.setCellRenderer(new LongDateCellRenderer()); - column.setMinWidth(TIME_COL_MIN_WIDTH); - column.setMaxWidth(TIME_COL_MAX_WIDTH); - column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - column.setWidth(TIME_COL_PREFERRED_WIDTH); - - column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()); - column.setCellRenderer(new PrioritizedIconCellRenderer()); - column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); - column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); - column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); - /* - * Allow sorting when a column header is clicked. - */ - pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel)); - - /* - * Create a row selection listener to enable/disable the Prioritize - * button. - */ - pendingTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { - if (e.getValueIsAdjusting()) { - return; - } - int row = pendingTable.getSelectedRow(); - - boolean enablePrioritizeButtons = false; - boolean enableDeprioritizeButtons = false; - if (row >= 0 && row < pendingTable.getRowCount()) { - enablePrioritizeButtons = true; - enableDeprioritizeButtons = (Integer) pendingTableModel.getValueAt(row, JobsTableModelColumns.PRIORITY.ordinal()) > 0; - } - this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); - this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); - this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); - this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); - }); - } - - /** - * Sets up the JTable that presents a view of the running jobs list for an - * auto ingest cluster. - */ - private void initRunningJobsTable() { - /* - * Remove some of the jobs table model columns from the JTable. This - * does not remove the columns from the model, just from this table. - */ - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); - /* - * Set up a column to display the cases associated with the jobs. - */ - TableColumn column; - column = runningTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the image folders associated with the - * jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the host names of the cluster nodes - * processing the jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()); - column.setMinWidth(NAME_COL_MIN_WIDTH); - column.setMaxWidth(NAME_COL_MAX_WIDTH); - column.setPreferredWidth(NAME_COL_PREFERRED_WIDTH); - column.setWidth(NAME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the ingest activities associated with the - * jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()); - column.setMinWidth(STAGE_COL_MIN_WIDTH); - column.setMaxWidth(STAGE_COL_MAX_WIDTH); - column.setPreferredWidth(STAGE_COL_PREFERRED_WIDTH); - column.setWidth(STAGE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the ingest activity times associated with - * the jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()); - column.setCellRenderer(new DurationCellRenderer()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(STAGE_TIME_COL_MAX_WIDTH); - column.setPreferredWidth(STAGE_TIME_COL_MIN_WIDTH); - column.setWidth(STAGE_TIME_COL_MIN_WIDTH); - - /* - * Prevent sorting when a column header is clicked. - */ - runningTable.setAutoCreateRowSorter(false); - } - - /** - * Sets up the JTable that presents a view of the completed jobs list for an - * auto ingest cluster. - */ - private void initCompletedJobsTable() { - /* - * Remove some of the jobs table model columns from the JTable. This - * does not remove the columns from the model, just from this table. - */ - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); - /* - * Set up a column to display the cases associated with the jobs. - */ - TableColumn column; - column = completedTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); - column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); - column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); - column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the image folders associated with the - * jobs. - */ - column = completedTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); - column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); - column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); - column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the create times of the jobs. - */ - column = completedTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); - column.setCellRenderer(new LongDateCellRenderer()); - column.setMinWidth(TIME_COL_MIN_WIDTH); - column.setMaxWidth(TIME_COL_MAX_WIDTH); - column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - column.setWidth(TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the completed times of the jobs. - */ - column = completedTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()); - column.setCellRenderer(new LongDateCellRenderer()); - column.setMinWidth(TIME_COL_MIN_WIDTH); - column.setMaxWidth(TIME_COL_MAX_WIDTH); - column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - column.setWidth(TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the statuses of the jobs, with a cell - * renderer that will choose an icon to represent the job status. - */ - column = completedTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()); - column.setCellRenderer(new StatusIconCellRenderer()); - column.setMinWidth(STATUS_COL_MIN_WIDTH); - column.setMaxWidth(STATUS_COL_MAX_WIDTH); - column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); - column.setWidth(STATUS_COL_PREFERRED_WIDTH); - /* - * Allow sorting when a column header is clicked. - */ - completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel)); - } - /** * Starts up the auto ingest monitor and adds this panel as an observer, * subscribes to services monitor events and starts a task to populate the @@ -437,10 +195,10 @@ final class AutoIngestDashboard extends JPanel implements Observer { private void startUp() throws AutoIngestMonitor.AutoIngestMonitorException { PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> { - + String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString(); String status = evt.getNewValue().toString(); - + if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) { status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); LOGGER.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS @@ -450,210 +208,62 @@ final class AutoIngestDashboard extends JPanel implements Observer { } else { LOGGER.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS } - + // if the status update is for an existing service who's status hasn't changed - do nothing. if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) { return; } - + statusByService.put(serviceDisplayName, status); displayServicesStatus(); }; - + // Subscribe to all multi-user services in order to display their status Set servicesList = new HashSet<>(); servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()); - servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); + servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); servicesList.add(ServicesMonitor.Service.MESSAGING.toString()); ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener); - + autoIngestMonitor = new AutoIngestMonitor(); autoIngestMonitor.addObserver(this); - autoIngestMonitor.startUp(); + new Thread(() -> { + try { + autoIngestMonitor.startUp(); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + LOGGER.log(Level.SEVERE, "Unable to start up Auto Ingest Monitor", ex); + } + }).start(); + } + + /** + * Shut down parts of the AutoIngestDashboard which were initialized + */ + void shutDown() { + if (autoIngestMonitor != null) { + autoIngestMonitor.shutDown(); + } } @Override public void update(Observable observable, Object arg) { - EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); + if (arg instanceof JobsSnapshot) { + EventQueue.invokeLater(() -> { + refreshTables(); + }); + } } /** * Reloads the table models using a jobs snapshot and refreshes the JTables * that use the models. * - * @param jobsSnapshot The jobs snapshot. + * @param nodeStateSnapshot The jobs snapshot. */ - private void refreshTables(JobsSnapshot jobsSnapshot) { - List pendingJobs = jobsSnapshot.getPendingJobs(); - List runningJobs = jobsSnapshot.getRunningJobs(); - List completedJobs = jobsSnapshot.getCompletedJobs(); - pendingJobs.sort(new AutoIngestJob.PriorityComparator()); - runningJobs.sort(new AutoIngestJob.DataSourceFileNameComparator()); - completedJobs.sort(new AutoIngestJob.CompletedDateDescendingComparator()); - refreshTable(pendingJobs, pendingTable, pendingTableModel); - refreshTable(runningJobs, runningTable, runningTableModel); - refreshTable(completedJobs, completedTable, completedTableModel); - } - - /** - * Reloads the table model for an auto ingest jobs table and refreshes the - * JTable that uses the model. - * - * @param jobs The list of auto ingest jobs. - * @param tableModel The table model. - * @param comparator An optional comparator (may be null) for sorting the - * table model. - */ - private void refreshTable(List jobs, JTable table, DefaultTableModel tableModel) { - try { - Path currentRow = getSelectedEntry(table, tableModel); - tableModel.setRowCount(0); - for (AutoIngestJob job : jobs) { - AutoIngestJob.StageDetails status = job.getProcessingStageDetails(); - tableModel.addRow(new Object[]{ - job.getManifest().getCaseName(), // CASE - job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME - job.getManifest().getDateFileCreated(), // CREATED_TIME - job.getProcessingStageStartDate(), // STARTED_TIME - job.getCompletedDate(), // COMPLETED_TIME - status.getDescription(), // STAGE - job.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, // STATUS - ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME - job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - job.getManifest().getFilePath(), // MANIFEST_FILE_PATH - job.getPriority(), // PRIORITY - job - }); - } - setSelectedEntry(table, tableModel, currentRow); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Error refreshing table " + table.toString(), ex); - } - } - - /** - * Gets a path representing the current selection in a table. - * - * @param table The table. - * @param tableModel The table model of the table. - * - * @return A path representing the current selection, or null if there is no - * selection. - */ - Path getSelectedEntry(JTable table, DefaultTableModel tableModel) { - try { - int currentlySelectedRow = table.getSelectedRow(); - if (currentlySelectedRow >= 0 && currentlySelectedRow < table.getRowCount()) { - return Paths.get(tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.CASE.ordinal()).toString(), - tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); - } - } catch (Exception ignored) { - return null; - } - return null; - } - - /** - * Sets the selection of the table to the passed-in path's item, if that - * item exists in the table. If it does not, clears the table selection. - * - * @param table The table. - * @param tableModel The table model of the table. - * @param path The path of the item to set - */ - void setSelectedEntry(JTable table, DefaultTableModel tableModel, Path path) { - if (path != null) { - try { - for (int row = 0; row < table.getRowCount(); ++row) { - Path temp = Paths.get(tableModel.getValueAt(row, JobsTableModelColumns.CASE.ordinal()).toString(), - tableModel.getValueAt(row, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); - if (temp.compareTo(path) == 0) { // found it - table.setRowSelectionInterval(row, row); - return; - } - } - } catch (Exception ignored) { - table.clearSelection(); - } - } - table.clearSelection(); - } - - /* - * This enum is used in conjunction with the DefaultTableModel class to - * provide table models for the JTables used to display a view of the - * pending jobs queue, running jobs list, and completed jobs list for an - * auto ingest cluster. The enum allows the columns of the table model to be - * described by either an enum ordinal or a column header string. - */ - private enum JobsTableModelColumns { - @Messages({"AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority=Prioritized"}) - - CASE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Case")), - DATA_SOURCE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder")), - HOST_NAME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName")), - CREATED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CreatedTime")), - STARTED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime")), - COMPLETED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime")), - STAGE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage")), - STAGE_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime")), - STATUS(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Status")), - CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder")), - MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath")), - PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority")), - JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Job")); - - private final String header; - - private JobsTableModelColumns(String header) { - this.header = header; - } - - private String getColumnHeader() { - return header; - } - - private static final String[] headers = { - CASE.getColumnHeader(), - DATA_SOURCE.getColumnHeader(), - HOST_NAME.getColumnHeader(), - CREATED_TIME.getColumnHeader(), - STARTED_TIME.getColumnHeader(), - COMPLETED_TIME.getColumnHeader(), - STAGE.getColumnHeader(), - STATUS.getColumnHeader(), - STAGE_TIME.getColumnHeader(), - CASE_DIRECTORY_PATH.getColumnHeader(), - MANIFEST_FILE_PATH.getColumnHeader(), - PRIORITY.getColumnHeader(), - JOB.getColumnHeader() - }; - }; - - /** - * A task that refreshes the UI components on this panel to reflect a - * snapshot of the pending, running and completed auto ingest jobs lists of - * an auto ingest cluster. - */ - private class RefreshComponentsTask implements Runnable { - - private final JobsSnapshot jobsSnapshot; - - /** - * Constructs a task that refreshes the UI components on this panel to - * reflect a snapshot of the pending, running and completed auto ingest - * jobs lists of an auto ingest cluster. - * - * @param jobsSnapshot The jobs snapshot. - */ - RefreshComponentsTask(JobsSnapshot jobsSnapshot) { - this.jobsSnapshot = jobsSnapshot; - } - - @Override - public void run() { - refreshTables(jobsSnapshot); - } + void refreshTables() { + pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); } /** @@ -687,6 +297,11 @@ final class AutoIngestDashboard extends JPanel implements Observer { } + static boolean isAdminAutoIngestDashboard() { + File f = new File(ADMIN_ACCESS_FILE_PATH); + return f.exists(); + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -698,75 +313,22 @@ final class AutoIngestDashboard extends JPanel implements Observer { jButton1 = new javax.swing.JButton(); pendingScrollPane = new javax.swing.JScrollPane(); - pendingTable = new javax.swing.JTable(); runningScrollPane = new javax.swing.JScrollPane(); - runningTable = new javax.swing.JTable(); completedScrollPane = new javax.swing.JScrollPane(); - completedTable = new javax.swing.JTable(); lbPending = new javax.swing.JLabel(); lbRunning = new javax.swing.JLabel(); lbCompleted = new javax.swing.JLabel(); refreshButton = new javax.swing.JButton(); lbServicesStatus = new javax.swing.JLabel(); tbServicesStatusMessage = new javax.swing.JTextField(); - prioritizeJobButton = new javax.swing.JButton(); - prioritizeCaseButton = new javax.swing.JButton(); clusterMetricsButton = new javax.swing.JButton(); - deprioritizeJobButton = new javax.swing.JButton(); - deprioritizeCaseButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N - pendingTable.setModel(pendingTableModel); - pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N - pendingTable.setRowHeight(20); - pendingTable.setSelectionModel(new DefaultListSelectionModel() { - private static final long serialVersionUID = 1L; - @Override - public void setSelectionInterval(int index0, int index1) { - if (index0 == pendingTable.getSelectedRow()) { - pendingTable.clearSelection(); - } else { - super.setSelectionInterval(index0, index1); - } - } - }); - pendingTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - pendingScrollPane.setViewportView(pendingTable); - - runningTable.setModel(runningTableModel); - runningTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.runningTable.toolTipText")); // NOI18N - runningTable.setRowHeight(20); - runningTable.setSelectionModel(new DefaultListSelectionModel() { - private static final long serialVersionUID = 1L; - @Override - public void setSelectionInterval(int index0, int index1) { - if (index0 == runningTable.getSelectedRow()) { - runningTable.clearSelection(); - } else { - super.setSelectionInterval(index0, index1); - } - } - }); - runningTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - runningScrollPane.setViewportView(runningTable); - - completedTable.setModel(completedTableModel); - completedTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.completedTable.toolTipText")); // NOI18N - completedTable.setRowHeight(20); - completedTable.setSelectionModel(new DefaultListSelectionModel() { - private static final long serialVersionUID = 1L; - @Override - public void setSelectionInterval(int index0, int index1) { - if (index0 == completedTable.getSelectedRow()) { - completedTable.clearSelection(); - } else { - super.setSelectionInterval(index0, index1); - } - } - }); - completedTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - completedScrollPane.setViewportView(completedTable); + pendingScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + pendingScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + pendingScrollPane.setOpaque(false); + pendingScrollPane.setPreferredSize(new java.awt.Dimension(2, 215)); lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(lbPending, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.lbPending.text")); // NOI18N @@ -793,24 +355,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { tbServicesStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.text")); // NOI18N tbServicesStatusMessage.setBorder(null); - org.openide.awt.Mnemonics.setLocalizedText(prioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.text")); // NOI18N - prioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.toolTipText")); // NOI18N - prioritizeJobButton.setEnabled(false); - prioritizeJobButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - prioritizeJobButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(prioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.text")); // NOI18N - prioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.toolTipText")); // NOI18N - prioritizeCaseButton.setEnabled(false); - prioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - prioritizeCaseButtonActionPerformed(evt); - } - }); - org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.clusterMetricsButton.text")); // NOI18N clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -818,24 +362,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { } }); - org.openide.awt.Mnemonics.setLocalizedText(deprioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.text")); // NOI18N - deprioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.toolTipText")); // NOI18N - deprioritizeJobButton.setEnabled(false); - deprioritizeJobButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - deprioritizeJobButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(deprioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.text")); // NOI18N - deprioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.toolTipText")); // NOI18N - deprioritizeCaseButton.setEnabled(false); - deprioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - deprioritizeCaseButtonActionPerformed(evt); - } - }); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -843,35 +369,27 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(pendingScrollPane) + .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(lbServicesStatus) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 861, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(prioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deprioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(prioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deprioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(clusterMetricsButton)) .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(lbServicesStatus) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(0, 0, Short.MAX_VALUE))) + .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING)) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(clusterMetricsButton))) .addContainerGap()) ); - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, prioritizeCaseButton, prioritizeJobButton, refreshButton}); + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, refreshButton}); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -883,23 +401,19 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(1, 1, 1) - .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lbRunning) .addGap(1, 1, 1) - .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(runningScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 133, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lbCompleted) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(completedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(refreshButton) - .addComponent(prioritizeJobButton) - .addComponent(prioritizeCaseButton) - .addComponent(clusterMetricsButton) - .addComponent(deprioritizeJobButton) - .addComponent(deprioritizeCaseButton)) + .addComponent(clusterMetricsButton)) .addContainerGap()) ); }// //GEN-END:initComponents @@ -913,135 +427,25 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - JobsSnapshot jobsSnapshot = autoIngestMonitor.refreshJobsSnapshot(); - refreshTables(jobsSnapshot); + refreshTables(); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_refreshButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."}) - private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.prioritizeJob(job); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath()); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } - }//GEN-LAST:event_prioritizeJobButtonActionPerformed - - @Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."}) - private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } - }//GEN-LAST:event_prioritizeCaseButtonActionPerformed - private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed new AutoIngestMetricsDialog(this.getTopLevelAncestor()); }//GEN-LAST:event_clusterMetricsButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."}) - private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.deprioritizeJob(job); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath()); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } - }//GEN-LAST:event_deprioritizeJobButtonActionPerformed - - @Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."}) - private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } - }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton clusterMetricsButton; private javax.swing.JScrollPane completedScrollPane; - private javax.swing.JTable completedTable; - private javax.swing.JButton deprioritizeCaseButton; - private javax.swing.JButton deprioritizeJobButton; private javax.swing.JButton jButton1; private javax.swing.JLabel lbCompleted; private javax.swing.JLabel lbPending; private javax.swing.JLabel lbRunning; private javax.swing.JLabel lbServicesStatus; private javax.swing.JScrollPane pendingScrollPane; - private javax.swing.JTable pendingTable; - private javax.swing.JButton prioritizeCaseButton; - private javax.swing.JButton prioritizeJobButton; private javax.swing.JButton refreshButton; private javax.swing.JScrollPane runningScrollPane; - private javax.swing.JTable runningTable; private javax.swing.JTextField tbServicesStatusMessage; // End of variables declaration//GEN-END:variables - - private class AutoIngestTableModel extends DefaultTableModel { - - private static final long serialVersionUID = 1L; - - private AutoIngestTableModel(String[] headers, int i) { - super(headers, i); - } - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - @Override - public Class getColumnClass(int columnIndex) { - if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) { - return Integer.class; - } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal() - || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal() - || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal() - || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) { - return Date.class; - } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) { - return Boolean.class; - } else { - return super.getColumnClass(columnIndex); - } - } - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form index 63887aceb0..5f3eab1a5f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 83bad29a99..195df4e4e7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.Component; +import java.awt.EventQueue; import java.util.List; import java.util.logging.Level; import java.util.stream.Collectors; @@ -38,8 +40,8 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; ) @TopComponent.Registration(mode = "dashboard", openAtStartup = false) @Messages({ - "CTL_AutoIngestDashboardAction=Auto Ingest Dashboard", - "CTL_AutoIngestDashboardTopComponent=Auto Ingest Dashboard"}) + "CTL_AutoIngestDashboardAction=Auto Ingest Jobs", + "CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs"}) public final class AutoIngestDashboardTopComponent extends TopComponent { private static final long serialVersionUID = 1L; @@ -74,8 +76,14 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { AutoIngestDashboard dashboard = AutoIngestDashboard.createDashboard(); tc.add(dashboard); dashboard.setSize(dashboard.getPreferredSize()); - + //if the user has administrator access enabled open the Node Status top component as well + if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { + EventQueue.invokeLater(() -> { + AinStatusDashboardTopComponent.openTopComponent(dashboard.getMonitor()); + }); + } tc.open(); + } tc.toFront(); tc.requestActive(); @@ -86,17 +94,24 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { } } - public static void closeTopComponent() { + @Override + protected void componentClosed() { if (topComponentInitialized) { final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (tc != null) { try { + for (Component comp : getComponents()) { + if (comp instanceof AutoIngestDashboard) { + ((AutoIngestDashboard) comp).shutDown(); + } + } tc.close(); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS } } } + super.componentClosed(); } public AutoIngestDashboardTopComponent() { @@ -104,6 +119,20 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { setName(Bundle.CTL_AutoIngestDashboardTopComponent()); } + /** + * Get the current AutoIngestDashboard if there is one. + * + * @return the current AutoIngestDashboard or null if there is not one + */ + AutoIngestDashboard getAutoIngestDashboard() { + for (Component comp : getComponents()) { + if (comp instanceof AutoIngestDashboard) { + return (AutoIngestDashboard) comp; + } + } + return null; + } + @Override public List availableModes(List modes) { /* diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java new file mode 100644 index 0000000000..60f84e566c --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -0,0 +1,229 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import javax.swing.Action; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; +import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; +import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; + +/** + * A node which represents all AutoIngestJobs of a given AutoIngestJobStatus. + * Each job with the specified status will have a child node representing it. + */ +final class AutoIngestJobsNode extends AbstractNode { + + @Messages({ + "AutoIngestJobsNode.caseName.text=Case Name", + "AutoIngestJobsNode.dataSource.text=Data Source", + "AutoIngestJobsNode.hostName.text=Host Name", + "AutoIngestJobsNode.stage.text=Stage", + "AutoIngestJobsNode.stageTime.text=Time in Stage", + "AutoIngestJobsNode.jobCreated.text=Job Created", + "AutoIngestJobsNode.jobCompleted.text=Job Completed", + "AutoIngestJobsNode.priority.text=Prioritized", + "AutoIngestJobsNode.status.text=Status" + }) + + /** + * Construct a new AutoIngestJobsNode. + */ + AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status) { + super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status), false)); + } + + /** + * A ChildFactory for generating JobNodes. + */ + static class AutoIngestNodeChildren extends ChildFactory { + + private final AutoIngestJobStatus autoIngestJobStatus; + private final JobsSnapshot jobsSnapshot; + + /** + * Create children nodes for the AutoIngestJobsNode which will each + * represent a single AutoIngestJob + * + * @param snapshot the snapshot which contains the AutoIngestJobs + * @param status the status of the jobs being displayed + */ + AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status) { + jobsSnapshot = snapshot; + autoIngestJobStatus = status; + } + + @Override + protected boolean createKeys(List list) { + List jobs; + switch (autoIngestJobStatus) { + case PENDING_JOB: + jobs = jobsSnapshot.getPendingJobs(); + jobs.sort(new AutoIngestJob.PriorityComparator()); + break; + case RUNNING_JOB: + jobs = jobsSnapshot.getRunningJobs(); + break; + case COMPLETED_JOB: + jobs = jobsSnapshot.getCompletedJobs(); + break; + default: + jobs = new ArrayList<>(); + } + if (jobs != null && jobs.size() > 0) { + list.addAll(jobs); + } + return true; + } + + @Override + protected Node createNodeForKey(AutoIngestJob key) { + return new JobNode(key, autoIngestJobStatus); + } + + } + + /** + * A node which represents a single auto ingest job. + */ + static final class JobNode extends AbstractNode { + + private final AutoIngestJob autoIngestJob; + private final AutoIngestJobStatus jobStatus; + + /** + * Construct a new JobNode to represent an AutoIngestJob and its status. + * + * @param job - the AutoIngestJob being represented by this node + * @param status - the current status of the AutoIngestJob being + * represented + */ + JobNode(AutoIngestJob job, AutoIngestJobStatus status) { + super(Children.LEAF); + jobStatus = status; + autoIngestJob = job; + setName(autoIngestJob.getManifest().getCaseName()); + setDisplayName(autoIngestJob.getManifest().getCaseName()); + } + + /** + * Get the AutoIngestJob which this node represents. + * + * @return autoIngestJob + */ + AutoIngestJob getAutoIngestJob() { + return autoIngestJob; + } + + @Override + @Messages({"AutoIngestJobsNode.prioritized.true=Yes", + "AutoIngestJobsNode.prioritized.false=No" + }) + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), + autoIngestJob.getManifest().getCaseName())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); + switch (jobStatus) { + case PENDING_JOB: + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), + autoIngestJob.getManifest().getDateFileCreated())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), + autoIngestJob.getPriority() > 0 ? Bundle.AutoIngestJobsNode_prioritized_true() : Bundle.AutoIngestJobsNode_prioritized_false())); + break; + case RUNNING_JOB: + AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), + autoIngestJob.getProcessingHostName())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), + status.getDescription())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), + DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())))); + break; + case COMPLETED_JOB: + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), + autoIngestJob.getManifest().getDateFileCreated())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), + autoIngestJob.getCompletedDate())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), + autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); + break; + default: + } + return s; + } + + @Override + public Action[] getActions(boolean context) { + List actions = new ArrayList<>(); + if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { + switch (jobStatus) { + case PENDING_JOB: + actions.add(new PrioritizationAction.PrioritizeJobAction(autoIngestJob)); + actions.add(new PrioritizationAction.PrioritizeCaseAction(autoIngestJob)); + PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(autoIngestJob); + deprioritizeJobAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(deprioritizeJobAction); + PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(autoIngestJob); + deprioritizeCaseAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(deprioritizeCaseAction); + break; + case RUNNING_JOB: + actions.add(new AutoIngestAdminActions.ProgressDialogAction()); + actions.add(new AutoIngestAdminActions.CancelJobAction()); + actions.add(new AutoIngestAdminActions.CancelModuleAction()); + break; + case COMPLETED_JOB: + actions.add(new AutoIngestAdminActions.ReprocessJobAction()); + actions.add(new AutoIngestAdminActions.DeleteCaseAction()); + actions.add(new AutoIngestAdminActions.ShowCaseLogAction()); + break; + default: + } + } + return actions.toArray(new Action[actions.size()]); + } + } + + /** + * An enumeration used to indicate the current status of an auto ingest job + * node. + */ + enum AutoIngestJobStatus { + PENDING_JOB, //NON-NLS + RUNNING_JOB, //NON-NLS + COMPLETED_JOB //NON-NLS + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form new file mode 100644 index 0000000000..bde4db4324 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form @@ -0,0 +1,18 @@ + + +
+ + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java new file mode 100644 index 0000000000..843089f948 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -0,0 +1,211 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.Dimension; +import java.beans.PropertyVetoException; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionListener; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datamodel.EmptyNode; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; + +/** + * A panel which displays an outline view with all jobs for a specified status. + */ +final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + private static final int INITIAL_CASENAME_WIDTH = 170; + private static final int INITIAL_DATASOURCE_WIDTH = 270; + private static final int INITIAL_PRIORITIZED_WIDTH = 20; + private static final int INITIAL_STATUS_WIDTH = 20; + private static final int INVALID_INDEX = -1; + private final org.openide.explorer.view.OutlineView outlineView; + private final Outline outline; + private ExplorerManager explorerManager; + private final AutoIngestJobStatus status; + + /** + * Creates a new AutoIngestJobsPanel of the specified jobStatus + * + * @param jobStatus the status of the jbos to be displayed on this panel + */ + AutoIngestJobsPanel(AutoIngestJobStatus jobStatus) { + initComponents(); + status = jobStatus; + outlineView = new org.openide.explorer.view.OutlineView(); + outline = outlineView.getOutline(); + customize(); + } + + /** + * Set up the AutoIngestJobsPanel's so that its outlineView is displaying + * the correct columns for the specified AutoIngestJobStatus + */ + @Messages({"AutoIngestJobsPanel.waitNode.text=Please Wait..."}) + void customize() { + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestJobsNode_caseName_text()); + outline.setRowSelectionAllowed(false); //rows will be made selectable after table has been populated + outline.setFocusable(false); //table will be made focusable after table has been populated + if (null == explorerManager) { + explorerManager = new ExplorerManager(); + } + explorerManager.setRootContext(new EmptyNode(Bundle.AutoIngestJobsPanel_waitNode_text())); + int indexOfColumn; + switch (status) { + case PENDING_JOB: + outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), + Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text()); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_priority_text()); + if (indexOfColumn != INVALID_INDEX) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_PRIORITIZED_WIDTH); + } + break; + case RUNNING_JOB: + outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), + Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), + Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text()); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); + if (indexOfColumn != INVALID_INDEX) { + outline.setColumnSorted(indexOfColumn, true, 1); + } + break; + case COMPLETED_JOB: + outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), + Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), + Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text()); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_jobCompleted_text()); + if (indexOfColumn != INVALID_INDEX) { + outline.setColumnSorted(indexOfColumn, false, 1); + } + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_status_text()); + if (indexOfColumn != INVALID_INDEX) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_STATUS_WIDTH); + } + break; + default: + } + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + outline.setRootVisible(false); + + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); + if (indexOfColumn != INVALID_INDEX) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_CASENAME_WIDTH); + } + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_dataSource_text()); + if (indexOfColumn != INVALID_INDEX) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_DATASOURCE_WIDTH); + } + add(outlineView, java.awt.BorderLayout.CENTER); + } + + private int getColumnIndexByName(String columnName) { + for (int index = 0; index < outline.getColumnModel().getColumnCount(); index++) { + if (outline.getColumnModel().getColumn(index).getHeaderValue().toString().equals(columnName)) { + return index; + } + } + return INVALID_INDEX; + } + + @Override + public void setSize(Dimension d) { + super.setSize(d); + outlineView.setMaximumSize(new Dimension(400, 100)); + outline.setPreferredScrollableViewportSize(new Dimension(400, 100)); + } + + /** + * Add a list selection listener to the selection model of the outline being + * used in this panel. + * + * @param listener the ListSelectionListener to add + */ + void addListSelectionListener(ListSelectionListener listener) { + outline.getSelectionModel().addListSelectionListener(listener); + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + /** + * Update the contents of this AutoIngestJobsPanel while retaining currently + * selected node. + * + * @param jobsSnapshot - the JobsSnapshot which will provide the new + * contents + */ + void refresh(JobsSnapshot jobsSnapshot) { + synchronized (this) { + outline.setRowSelectionAllowed(false); + Node[] selectedNodes = explorerManager.getSelectedNodes(); + AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); + explorerManager.setRootContext(autoIngestNode); + outline.setRowSelectionAllowed(true); + if (selectedNodes.length > 0 && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored + try { + explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); + } catch (PropertyVetoException ignore) { + //Unable to select previously selected node + } + } + outline.setFocusable(true); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new java.awt.BorderLayout()); + }// //GEN-END:initComponents + + /** + * Get the AutoIngestJob for the currently selected node of this panel. + * + * @return AutoIngestJob which is currently selected in this panel + */ + AutoIngestJob getSelectedAutoIngestJob() { + Node[] selectedRows = explorerManager.getSelectedNodes(); + if (selectedRows.length == 1) { + return ((JobNode) selectedRows[0]).getAutoIngestJob(); + } + return null; + } + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 8a1228cde4..b7f90f100f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -133,7 +133,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Event.JOB_STATUS_UPDATED.toString(), Event.JOB_COMPLETED.toString(), Event.CASE_PRIORITIZED.toString(), - Event.JOB_STARTED.toString()})); + Event.JOB_STARTED.toString(), + Event.REPORT_STATE.toString()})); private static final long JOB_STATUS_EVENT_INTERVAL_SECONDS = 10; private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d"; private static final long MAX_MISSED_JOB_STATUS_UPDATES = 10; @@ -165,6 +166,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private volatile State state; private volatile ErrorState errorState; + private volatile AutoIngestNodeStateEvent lastPublishedStateEvent; + /** * Gets a singleton auto ingest manager responsible for processing auto * ingest jobs defined by manifest files that can be added to any level of a @@ -235,6 +238,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen jobStatusPublishingExecutor.scheduleWithFixedDelay(new PeriodicJobStatusEventTask(), JOB_STATUS_EVENT_INTERVAL_SECONDS, JOB_STATUS_EVENT_INTERVAL_SECONDS, TimeUnit.SECONDS); eventPublisher.addSubscriber(EVENT_LIST, instance); state = State.RUNNING; + + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RUNNING, LOCAL_HOST_NAME)); errorState = ErrorState.NONE; } @@ -275,6 +280,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen handleRemoteCasePrioritizationEvent((AutoIngestCasePrioritizedEvent) event); } else if (event instanceof AutoIngestCaseDeletedEvent) { handleRemoteCaseDeletedEvent((AutoIngestCaseDeletedEvent) event); + } else if (event instanceof AutoIngestRequestNodeStateEvent) { + handleRemoteRequestNodeStateEvent(); } } } @@ -377,7 +384,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } /** - * Processes a case deletin event from another node by triggering an + * Processes a case deletion event from another node by triggering an * immediate input directory scan. * * @param event A case deleted event from another auto ingest node. @@ -390,6 +397,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen notifyObservers(Event.CASE_DELETED); } + /** + * Handle a request for current state by re-sending the last state event. + */ + private void handleRemoteRequestNodeStateEvent() { + // Re-publish last state event. + eventPublisher.publishRemotely(lastPublishedStateEvent); + } + /** * Shuts down auto ingest. */ @@ -400,6 +415,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.INFO, "Auto ingest shutting down"); state = State.SHUTTING_DOWN; try { + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.SHUTTING_DOWN, AutoIngestManager.LOCAL_HOST_NAME)); eventPublisher.removeSubscriber(EVENT_LIST, instance); stopInputFolderScans(); stopJobProcessing(); @@ -1681,6 +1697,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen */ setChanged(); notifyObservers(Event.PAUSED_BY_REQUEST); + + /** + * Publish an event to let remote listeners know that the + * node has been paused. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_REQUEST, AutoIngestManager.LOCAL_HOST_NAME)); } } } @@ -1704,6 +1726,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen */ setChanged(); notifyObservers(Event.RESUMED); + + /** + * Publish an event to let remote listeners know that the + * node has been resumed. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME)); } pauseLock.notifyAll(); } @@ -1724,10 +1752,23 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen pauseRequested = false; setChanged(); notifyObservers(Event.PAUSED_BY_REQUEST); + + /** + * Publish an event to let remote listeners know that the + * node has been paused. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_REQUEST, AutoIngestManager.LOCAL_HOST_NAME)); + pauseLock.wait(); sysLogger.log(Level.INFO, "Job processing resumed after pause request"); setChanged(); notifyObservers(Event.RESUMED); + + /** + * Publish an event to let remote listeners know that the + * node has been resumed. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME)); } } } @@ -1744,11 +1785,24 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.SEVERE, "Job processing paused for system error"); setChanged(); notifyObservers(Event.PAUSED_FOR_SYSTEM_ERROR); + + /** + * Publish an event to let remote listeners know that the node + * has been paused. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_FOR_SYSTEM_ERROR, AutoIngestManager.LOCAL_HOST_NAME)); + pauseLock.wait(); errorState = ErrorState.NONE; sysLogger.log(Level.INFO, "Job processing resumed after system error"); setChanged(); notifyObservers(Event.RESUMED); + + /** + * Publish an event to let remote listeners know that the node + * has been resumed. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME)); } } @@ -2255,13 +2309,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Case.openAsCurrentCase(metadataFilePath.toString()); } else { caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); - + // Create the case directory now in case it is needed by selectSolrServerForCase Case.createCaseDirectory(caseDirectoryPath.toString(), CaseType.MULTI_USER_CASE); - + // If a list of servers exists, choose one to use for this case Server.selectSolrServerForCase(rootOutputDirectory, caseDirectoryPath); - + CaseDetails caseDetails = new CaseDetails(caseName); Case.createAsCurrentCase(CaseType.MULTI_USER_CASE, caseDirectoryPath.toString(), caseDetails); /* @@ -2272,7 +2326,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } currentJob.setCaseDirectoryPath(caseDirectoryPath); - Case caseForJob = Case.getOpenCase(); + Case caseForJob = Case.getCurrentCase(); sysLogger.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); return caseForJob; @@ -2280,13 +2334,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen throw new CaseManagementException(String.format("Error creating solr settings file for case %s for %s", caseName, manifest.getFilePath()), ex); } catch (CaseActionException ex) { throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, manifest.getFilePath()), ex); - } catch (NoCurrentCaseException ex) { - /* - * Deal with the unfortunate fact that - * Case.getOpenCase throws NoCurrentCaseException. - */ - throw new CaseManagementException(String.format("Error getting current case %s for %s", caseName, manifest.getFilePath()), ex); - } + } } else { throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", caseName, manifest.getFilePath())); } @@ -2490,7 +2538,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{manifestPath, selectedProcessor.getDataSourceType()}); selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), progressMonitor, callBack); ingestLock.wait(); - + // at this point we got the content object(s) from the current DSP. // check whether the data source was processed successfully if ((dataSource.getResultDataSourceProcessorResultCode() == CRITICAL_ERRORS) @@ -2500,7 +2548,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen logDataSourceProcessorResult(dataSource); continue; } - + logDataSourceProcessorResult(dataSource); return; } @@ -3016,7 +3064,11 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen CASE_DELETED, PAUSED_BY_REQUEST, PAUSED_FOR_SYSTEM_ERROR, - RESUMED + RESUMED, + STARTING_UP, + RUNNING, + SHUTTING_DOWN, + REPORT_STATE } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index b8c5fcf23a..229128dae1 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -25,11 +25,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Observable; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; @@ -38,7 +41,7 @@ import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus; - +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event; /** * An auto ingest monitor responsible for monitoring and reporting the * processing of auto ingest jobs. @@ -56,7 +59,13 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen AutoIngestManager.Event.JOB_STATUS_UPDATED.toString(), AutoIngestManager.Event.JOB_COMPLETED.toString(), AutoIngestManager.Event.CASE_PRIORITIZED.toString(), - AutoIngestManager.Event.JOB_STARTED.toString()})); + AutoIngestManager.Event.JOB_STARTED.toString(), + AutoIngestManager.Event.RUNNING.toString(), + AutoIngestManager.Event.PAUSED_BY_REQUEST.toString(), + AutoIngestManager.Event.PAUSED_FOR_SYSTEM_ERROR.toString(), + AutoIngestManager.Event.STARTING_UP.toString(), + AutoIngestManager.Event.SHUTTING_DOWN.toString(), + AutoIngestManager.Event.RESUMED.toString()})); private final AutopsyEventPublisher eventPublisher; private CoordinationService coordinationService; private final ScheduledThreadPoolExecutor coordSvcQueryExecutor; @@ -64,6 +73,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen @GuardedBy("jobsLock") private JobsSnapshot jobsSnapshot; + private final Map nodeStates = new ConcurrentHashMap<>(); + /** * Constructs an auto ingest monitor responsible for monitoring and * reporting the processing of auto ingest jobs. @@ -94,6 +105,9 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } coordSvcQueryExecutor.scheduleWithFixedDelay(new CoordinationServiceQueryTask(), 0, CORRD_SVC_QUERY_INERVAL_MINS, TimeUnit.MINUTES); eventPublisher.addSubscriber(EVENT_LIST, this); + + // Publish an event that asks running nodes to send their state. + eventPublisher.publishRemotely(new AutoIngestRequestNodeStateEvent(AutoIngestManager.Event.REPORT_STATE)); } /** @@ -130,6 +144,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen handleCasePrioritizationEvent((AutoIngestCasePrioritizedEvent) event); } else if (event instanceof AutoIngestCaseDeletedEvent) { handleCaseDeletedEvent((AutoIngestCaseDeletedEvent) event); + } else if (event instanceof AutoIngestNodeStateEvent) { + handleAutoIngestNodeStateEvent((AutoIngestNodeStateEvent) event); } } @@ -193,12 +209,31 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen /** * Handles a case deletion event. * - * @param event A job/case prioritization event. + * @param event A job/case deletion event. */ private void handleCaseDeletedEvent(AutoIngestCaseDeletedEvent event) { coordSvcQueryExecutor.submit(new CoordinationServiceQueryTask()); } + /** + * Handles an auto ingest node state change event. + * + * @param event A node state change event. + */ + private void handleAutoIngestNodeStateEvent(AutoIngestNodeStateEvent event) { + if (event.getEventType() == AutoIngestManager.Event.SHUTTING_DOWN) { + // Remove node from collection. + nodeStates.remove(event.getNodeName()); + } else { + // Otherwise either create an entry for the given node name or update + // an existing entry in the map. + nodeStates.put(event.getNodeName(), new AutoIngestNodeState(event.getNodeName(), event.getEventType())); + } + setChanged(); + // Trigger a dashboard refresh. + notifyObservers(nodeStates.get(event.getNodeName())); + } + /** * Gets the auto ingest monitor's current snapshot of the pending jobs * queue, running jobs list, and completed jobs list for an auto ingest @@ -212,6 +247,14 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } } + /** + * Gets the current state of known AIN's in the system. + * @return + */ + List getNodeStates() { + return nodeStates.values().stream().collect(Collectors.toList()); + } + /** * Makes the auto ingest monitor's refresh its current snapshot of the * pending jobs queue, running jobs list, and completed jobs list for an @@ -505,8 +548,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /** - * A snapshot of the pending jobs queue, running jobs list, and completed - * jobs list for an auto ingest cluster. + * A snapshot of the pending jobs queue, running jobs list and completed jobs + * list for an auto ingest cluster. */ static final class JobsSnapshot { @@ -622,6 +665,62 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } + /** + * Class that represents the state of an AIN for the dashboard. + */ + static final class AutoIngestNodeState { + + /** + * The set of AIN states. + */ + enum State { + STARTING_UP, + SHUTTING_DOWN, + RUNNING, + PAUSED_BY_REQUEST, + PAUSED_DUE_TO_SYSTEM_ERROR, + UNKNOWN + } + + private final String nodeName; + private final State nodeState; + + AutoIngestNodeState(String name, Event event) { + nodeName = name; + switch (event) { + case STARTING_UP: + nodeState = State.STARTING_UP; + break; + case SHUTTING_DOWN: + nodeState = State.SHUTTING_DOWN; + break; + case RUNNING: + nodeState = State.RUNNING; + break; + case PAUSED_BY_REQUEST: + nodeState = State.PAUSED_BY_REQUEST; + break; + case PAUSED_FOR_SYSTEM_ERROR: + nodeState = State.PAUSED_DUE_TO_SYSTEM_ERROR; + break; + case RESUMED: + nodeState = State.RUNNING; + break; + default: + nodeState = State.UNKNOWN; + break; + } + } + + String getName() { + return nodeName; + } + + State getState() { + return nodeState; + } + } + /** * Exception type thrown when there is an error completing an auto ingest * monitor operation. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeStateEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeStateEvent.java new file mode 100644 index 0000000000..d1a71c3c0a --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeStateEvent.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.io.Serializable; +import org.sleuthkit.autopsy.events.AutopsyEvent; + +/** + * Event published when an auto ingest node is started, paused, + * resumed or shutdown. + */ +public final class AutoIngestNodeStateEvent extends AutopsyEvent implements Serializable { + private static final long serialVersionUID = 1L; + private final AutoIngestManager.Event eventType; + private final String nodeName; + + public AutoIngestNodeStateEvent(AutoIngestManager.Event eventType, String nodeName) { + super(eventType.toString(), null, null); + this.eventType = eventType; + this.nodeName = nodeName; + } + + public AutoIngestManager.Event getEventType() { + return this.eventType; + } + + public String getNodeName() { + return this.nodeName; + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRequestNodeStateEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRequestNodeStateEvent.java new file mode 100644 index 0000000000..d756020520 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRequestNodeStateEvent.java @@ -0,0 +1,34 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.io.Serializable; +import org.sleuthkit.autopsy.events.AutopsyEvent; + +/** + * Event published to request that auto ingest nodes send their current state. + * This event is sent on auto ingest dashboard startup. + */ +public class AutoIngestRequestNodeStateEvent extends AutopsyEvent implements Serializable { + private static final long serialVersionUID = 1L; + + public AutoIngestRequestNodeStateEvent(AutoIngestManager.Event eventType) { + super(eventType.toString(), null, null); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index 633d069cc8..e4fe649e18 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -10,9 +10,6 @@ AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path -AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first -AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it -AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime=Time in Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job @@ -222,10 +219,6 @@ FileExporterSettingsPanel.SaveTooltip_1=Save the current rule AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables AutoIngestDashboard.refreshButton.text=&Refresh AutoIngestDashboard.jButton1.text=jButton1 -AutoIngestDashboard.prioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue. -AutoIngestDashboard.prioritizeJobButton.text=Prioritize &Job -AutoIngestDashboard.prioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue. -AutoIngestDashboard.prioritizeCaseButton.text=Prioritize &Case AutoIngestMetricsDialog.reportTextArea.text= AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report @@ -236,10 +229,6 @@ ArchiveFilePanel.browseButton.text=Browse ArchiveFilePanel.pathTextField.text= ArchiveFilePanel.errorLabel.text=Error Label AutoIngestMetricsDialog.startingDataLabel.text=Starting Date: -AutoIngestDashboard.deprioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue. -AutoIngestDashboard.deprioritizeJobButton.text=Deprioritize J&ob -AutoIngestDashboard.deprioritizeCaseButton.text=Deprioritize C&ase -AutoIngestDashboard.deprioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job AutoIngestControlPanel.bnPrioritizeCase.text=Prioritize Case @@ -269,3 +258,7 @@ AutoIngestControlPanel.bnPrioritizeJob.toolTipText=Move this folder to the top o AutoIngestControlPanel.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestControlPanel.bnPrioritizeJob.actionCommand=Prioritize Job AutoIngestControlPanel.bnDeprioritizeJob.actionCommand=Deprioritize Job +AinStatusDashboard.refreshButton.toolTipText=Refresh displayed tables +AinStatusDashboard.refreshButton.text=&Refresh +AinStatusDashboard.clusterMetricsButton.text=Auto Ingest &Metrics +AinStatusDashboard.nodeStatusTableTitle.text=Auto Ingest Nodes diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java index 0029150e3d..399ae574ba 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java @@ -375,7 +375,7 @@ final class FileExportRuleSet implements Serializable, Comparable evaluate(long dataSourceId) throws ExportRulesException { try { - SleuthkitCase db = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase db = Case.getCurrentCaseThrows().getSleuthkitCase(); try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId))) { ResultSet resultSet = queryResult.getResultSet(); List fileIds = new ArrayList<>(); @@ -1063,7 +1063,7 @@ final class FileExportRuleSet implements Serializable, Comparable dataSources) throws FileExportException { SleuthkitCase skCase; try { - skCase = Case.getOpenCase().getSleuthkitCase(); + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); } catch (NoCurrentCaseException ex) { throw new FileExportException("Exception while getting open case.", ex); } @@ -341,7 +341,7 @@ final class FileExporter { * storage. */ private void exportFile(Long fileId, List ruleNames, Supplier cancelCheck) throws TskCoreException, IOException, NoCurrentCaseException { - AbstractFile file = Case.getOpenCase().getSleuthkitCase().getAbstractFileById(fileId); + AbstractFile file = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(fileId); if (!shouldExportFile(file)) { return; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java index 62b01b558f..cea7338f51 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java @@ -529,7 +529,7 @@ public final class FileExporterSettingsPanel extends JPanel { void populateArtifacts() { Set artifactTypes = scanRulesForArtifacts(); try { - SleuthkitCase currentCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase currentCase = Case.getCurrentCaseThrows().getSleuthkitCase(); for (BlackboardArtifact.Type type : currentCase.getArtifactTypes()) { artifactTypes.add(type.getTypeName()); } @@ -603,7 +603,7 @@ public final class FileExporterSettingsPanel extends JPanel { Set attributeTypes = scanRulesForAttributes(); try { - SleuthkitCase currentCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase currentCase = Case.getCurrentCaseThrows().getSleuthkitCase(); for (BlackboardAttribute.Type type : currentCase.getAttributeTypes()) { attributeTypes.add(type.getTypeName()); attributeTypeMap.put(type.getTypeName(), type.getValueType()); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java new file mode 100644 index 0000000000..d50a85f906 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -0,0 +1,249 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.Cursor; +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +/** + * Abstract actions which are for the modification of AutoIngestJob or Case + * priority. + */ +abstract class PrioritizationAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(PrioritizationAction.class.getName()); + private final AutoIngestJob job; + + /** + * Construct a new Prioritization action for the selected job + * + * @param selectedJob The job which will be used to determine what has it's + * priority modified + * @param title - the string to represent the action in menus + */ + PrioritizationAction(AutoIngestJob selectedJob, String title) { + super(title); + job = selectedJob; + } + + /** + * The implementation specific method which modifies job or case priority + * + * @param monitor - the AutoIngestMonitor which can be accessed to change + * the job or case priority + * + * @throws + * org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestMonitorException + */ + protected abstract void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException; + + /** + * Get the implementation specific error message for if modifyPriority fails + * + * @return the error message for the current implementation + */ + protected abstract String getErrorMessage(); + + /** + * Gets the job this action is constructed for + * + * @return job - the AutoIngestJob + */ + protected AutoIngestJob getJob() { + return job; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (job != null) { + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc != null) { + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + dashboard.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EventQueue.invokeLater(() -> { + try { + modifyPriority(dashboard.getMonitor()); + dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = getErrorMessage(); + logger.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } finally { + dashboard.getPendingJobsPanel().setCursor(Cursor.getDefaultCursor()); + } + }); + } + } + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + + /** + * Action to prioritize the specified AutoIngestJob + */ + @Messages({"PrioritizationAction.prioritizeJobAction.title=Prioritize Job", + "PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job \"%s\"."}) + static final class PrioritizeJobAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new PrioritizeJobAction + * + * @param selectedJob - the AutoIngestJob to be prioritized + */ + PrioritizeJobAction(AutoIngestJob selectedJob) { + super(selectedJob, Bundle.PrioritizationAction_prioritizeJobAction_title()); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.prioritizeJob(getJob()); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.PrioritizationAction_prioritizeJobAction_error(), getJob().getManifest().getFilePath()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + /** + * Action to deprioritize the specified AutoIngestJob + */ + @Messages({"PrioritizationAction.deprioritizeJobAction.title=Deprioritize Job", + "PrioritizationAction.deprioritizeJobAction.error=Failed to deprioritize job \"%s\"."}) + static final class DeprioritizeJobAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new DeprioritizeJobAction + * + * @param selectedJob - the AutoIngestJob to be deprioritized + */ + DeprioritizeJobAction(AutoIngestJob selectedJob) { + super(selectedJob, Bundle.PrioritizationAction_deprioritizeJobAction_title()); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.deprioritizeJob(getJob()); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.PrioritizationAction_deprioritizeJobAction_error(), getJob().getManifest().getFilePath()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + /** + * Action to prioritize all jobs for the case which the specified + * AutoIngestJob is a part of. + */ + @Messages({"PrioritizationAction.prioritizeCaseAction.title=Prioritize Case", + "PrioritizationAction.prioritizeCaseAction.error==Failed to prioritize case \"%s\"."}) + static final class PrioritizeCaseAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new PrioritizeCaseAction + * + * @param selectedJob - the AutoIngestJob which should have it's case + * prioritized + */ + PrioritizeCaseAction(AutoIngestJob selectedJob) { + super(selectedJob, Bundle.PrioritizationAction_prioritizeCaseAction_title()); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.prioritizeCase(getJob().getManifest().getCaseName()); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.PrioritizationAction_prioritizeCaseAction_error(), getJob().getManifest().getCaseName()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + /** + * Action to deprioritize all jobs for the case which the specified + * AutoIngestJob is a part of. + */ + @Messages({"PrioritizationAction.deprioritizeCaseAction.title=Deprioritize Case", + "PrioritizationAction.deprioritizeCaseAction.error=Failed to deprioritize case \"%s\"."}) + static final class DeprioritizeCaseAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new DeprioritizeCaseAction + * + * @param selectedJob - the AutoIngestJob which should have it's case + * deprioritized + */ + DeprioritizeCaseAction(AutoIngestJob selectedJob) { + super(selectedJob, Bundle.PrioritizationAction_deprioritizeCaseAction_title()); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.deprioritizeCase(getJob().getManifest().getCaseName()); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.PrioritizationAction_deprioritizeCaseAction_error(), getJob().getManifest().getCaseName()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java index 7490c3e7b1..bcae2b3153 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java @@ -49,6 +49,7 @@ final class AddMemoryImageTask implements Runnable { private final DataSourceProcessorCallback callback; private volatile VolatilityProcessor volatilityProcessor; private volatile boolean isCancelled; + private final String profile; // empty for autodetect /** * Constructs a runnable that adds a memory image to a case database. @@ -57,6 +58,7 @@ final class AddMemoryImageTask implements Runnable { * associated with the data source that is intended * to be unique across multiple cases (e.g., a UUID). * @param memoryImagePath Path to the memory image file. + * @param profile Volatility profile to run or empty string to autodetect * @param pluginsToRun The Volatility plugins to run. * @param timeZone The time zone to use when processing dates and * times for the image, obtained from @@ -65,9 +67,10 @@ final class AddMemoryImageTask implements Runnable { * during processing. * @param callback Callback to call when processing is done. */ - AddMemoryImageTask(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddMemoryImageTask(String deviceId, String memoryImagePath, String profile, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; this.memoryImagePath = memoryImagePath; + this.profile = profile; this.pluginsToRun = pluginsToRun; this.timeZone = timeZone; this.callback = callback; @@ -94,7 +97,7 @@ final class AddMemoryImageTask implements Runnable { try { Image dataSource = addImageToCase(); dataSources.add(dataSource); - volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, pluginsToRun, progressMonitor); + volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, profile, pluginsToRun, progressMonitor); volatilityProcessor.run(); } catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) { criticalErrorOccurred = true; @@ -142,7 +145,7 @@ final class AddMemoryImageTask implements Runnable { private Image addImageToCase() throws NoCurrentCaseException, TskCoreException { progressMonitor.setProgressText(Bundle.AddMemoryImageTask_progressMessage_addingImageFile( memoryImagePath)); - SleuthkitCase caseDatabase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase caseDatabase = Case.getCurrentCaseThrows().getSleuthkitCase(); caseDatabase.acquireSingleUserCaseWriteLock(); try { /* diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties index 007b78dc5a..1c7d3312da 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties @@ -3,11 +3,10 @@ # and open the template in the editor. MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName=Browse for a memory image file: -MemoryDSInputPanel.PluginsToRunLabel.text=Available plugins to run: -MemoryDSInputPanel.volExecutableLabel.text=Version of Volatility to Run: +MemoryDSInputPanel.PluginsToRunLabel.text=Plugins to run: MemoryDSInputPanel.pathLabel.text=Browse for a memory image file: MemoryDSInputPanel.pathTextField.text= MemoryDSInputPanel.errorLabel.text=Error Label MemoryDSInputPanel.browseButton.text=Browse -MemoryDSImputPanel.pathTextField.text= -MemoryDSInputPanel.timeZoneLabel.text=Please select the input timezone: \ No newline at end of file +MemoryDSInputPanel.timeZoneLabel.text=Timezone: +MemoryDSInputPanel.profileLabel.text=Profile: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form index 123f9b308b..f899eeb503 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form @@ -28,13 +28,15 @@ - - + + - - + + + + @@ -43,8 +45,8 @@ - + @@ -66,17 +68,17 @@ - + - - + + - + - + @@ -85,7 +87,7 @@ - + @@ -97,14 +99,17 @@ - + + + + - + @@ -117,14 +122,14 @@ - + - + @@ -139,27 +144,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -174,7 +158,7 @@ - + @@ -189,5 +173,20 @@ + + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index f96a9cc801..a26136fd1a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -26,7 +26,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SimpleTimeZone; +import java.util.SortedSet; import java.util.TimeZone; +import java.util.TreeSet; import javax.swing.JFileChooser; import javax.swing.JPanel; import javax.swing.JTable; @@ -42,6 +44,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MemoryDSInputPanel extends JPanel implements DocumentListener { private static final long serialVersionUID = 1L; //default @@ -53,13 +56,26 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private final PluginListTableModel tableModel = new PluginListTableModel(); private final List PluginListNames = new ArrayList<>(); private final Map pluginListStates = new HashMap<>(); // is set by listeners when users select and deselect items - private final Boolean isEnabled = true; + private final SortedSet profileList = new TreeSet<>(Arrays.asList( + "VistaSP0x64", "VistaSP0x86", "VistaSP1x64", "VistaSP1x86", + "VistaSP2x64", "VistaSP2x86", "Win10x64", "Win10x64_10586", + "Win10x64_14393", "Win10x86", "Win10x86_10586", "Win10x86_14393", + "Win2003SP0x86", "Win2003SP1x64", "Win2003SP1x86", "Win2003SP2x64", + "Win2003SP2x86", "Win2008R2SP0x64", "Win2008R2SP1x64", "Win2008R2SP1x64_23418", + "Win2008SP1x64", "Win2008SP1x86", "Win2008SP2x64", "Win2008SP2x86", + "Win2012R2x64", "Win2012R2x64_18340", "Win2012x64", "Win2016x64_14393", + "Win7SP0x64", "Win7SP0x86", "Win7SP1x64", "Win7SP1x64_23418", "Win7SP1x86_23418", + "Win81U1x64", "Win81U1x86", "Win8SP0x64", "Win8SP0x86", "Win8SP1x64", + "Win8SP1x64_18340", "Win8SP1x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP2x86", + "WinXPSP3x86")); + private final static String AUTODETECT_PROFILE = "Auto Detect"; + /** * Creates new MemoryDSInputPanel panel for user input */ private MemoryDSInputPanel(String context) { - this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo"}; + this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo", "dlldump", "moddump", "procdump", "dumpfiles", "dumpregistry"}; Arrays.sort(this.pluginList); initComponents(); @@ -82,7 +98,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { instance.postInit(); instance.customizePluginListTable(); instance.createTimeZoneList(); - instance.createVolatilityVersionList(); + instance.populateProfileCombobox(); instance.createPluginList(); return instance; @@ -95,14 +111,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } private void customizePluginListTable() { - PluginList.setModel(tableModel); - PluginList.setTableHeader(null); - PluginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + pluginTable.setModel(tableModel); + pluginTable.setTableHeader(null); + pluginTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); final int width = listsScrollPane.getPreferredSize().width; - PluginList.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + pluginTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); TableColumn column; - for (int i = 0; i < PluginList.getColumnCount(); i++) { - column = PluginList.getColumnModel().getColumn(i); + for (int i = 0; i < pluginTable.getColumnCount(); i++) { + column = pluginTable.getColumnModel().getColumn(i); if (i == 0) { column.setPreferredWidth(((int) (width * 0.07))); } else { @@ -138,11 +154,12 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { timeZoneComboBox.setSelectedItem(formatted); } - private void createVolatilityVersionList() { - - volExecutableComboBox.addItem("2.6"); - volExecutableComboBox.addItem("2.5"); - + + private void populateProfileCombobox() { + profileComboBox.addItem(AUTODETECT_PROFILE); + profileList.forEach((profile) -> { + profileComboBox.addItem(profile); + }); } private void createPluginList() { @@ -157,8 +174,10 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { PluginListNames.add(plugin); if (allEnabled) { pluginListStates.put(plugin, true); + } else if ((pluginMap.containsKey(plugin) && pluginMap.get(plugin).equals("false"))) { + pluginListStates.put(plugin, false); } else { - pluginListStates.put(plugin, pluginMap.containsKey(plugin)); + pluginListStates.put(plugin, true); } } tableModel.fireTableDataChanged(); @@ -181,15 +200,20 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { errorLabel = new javax.swing.JLabel(); timeZoneLabel = new javax.swing.JLabel(); timeZoneComboBox = new javax.swing.JComboBox<>(); - volExecutableLabel = new javax.swing.JLabel(); - volExecutableComboBox = new javax.swing.JComboBox<>(); PluginsToRunLabel = new javax.swing.JLabel(); listsScrollPane = new javax.swing.JScrollPane(); - PluginList = new javax.swing.JTable(); + pluginTable = new javax.swing.JTable(); + profileLabel = new javax.swing.JLabel(); + profileComboBox = new javax.swing.JComboBox<>(); org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathTextField.text")); // NOI18N + pathTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pathTextFieldActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N browseButton.addActionListener(new java.awt.event.ActionListener() { @@ -205,18 +229,9 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { timeZoneComboBox.setMaximumRowCount(30); - org.openide.awt.Mnemonics.setLocalizedText(volExecutableLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.volExecutableLabel.text")); // NOI18N - - volExecutableComboBox.setEnabled(false); - volExecutableComboBox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - volExecutableComboBoxActionPerformed(evt); - } - }); - org.openide.awt.Mnemonics.setLocalizedText(PluginsToRunLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.PluginsToRunLabel.text")); // NOI18N - PluginList.setModel(new javax.swing.table.DefaultTableModel( + pluginTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { {}, {}, @@ -227,7 +242,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } )); - listsScrollPane.setViewportView(PluginList); + listsScrollPane.setViewportView(pluginTable); + + org.openide.awt.Mnemonics.setLocalizedText(profileLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.profileLabel.text")); // NOI18N + + profileComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + profileComboBoxActionPerformed(evt); + } + }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -241,18 +264,19 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(pathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 218, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(profileComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, 243, Short.MAX_VALUE) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))) .addGap(0, 163, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(errorLabel) - .addComponent(volExecutableLabel) - .addComponent(PluginsToRunLabel)) + .addComponent(PluginsToRunLabel) + .addComponent(profileLabel)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -269,15 +293,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(errorLabel) - .addGap(18, 18, 18) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(volExecutableLabel) - .addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(profileLabel) + .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(PluginsToRunLabel) - .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(30, Short.MAX_VALUE)) + .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(73, Short.MAX_VALUE)) ); pathLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName")); // NOI18N @@ -298,12 +322,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } }//GEN-LAST:event_browseButtonActionPerformed - private void volExecutableComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_volExecutableComboBoxActionPerformed + private void profileComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_profileComboBoxActionPerformed // TODO add your handling code here: - }//GEN-LAST:event_volExecutableComboBoxActionPerformed + }//GEN-LAST:event_profileComboBoxActionPerformed + + private void pathTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pathTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_pathTextFieldActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTable PluginList; private javax.swing.JLabel PluginsToRunLabel; private javax.swing.JButton browseButton; private javax.swing.JLabel errorLabel; @@ -311,10 +338,11 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private javax.swing.JScrollPane listsScrollPane; private javax.swing.JLabel pathLabel; private javax.swing.JTextField pathTextField; + private javax.swing.JTable pluginTable; + private javax.swing.JComboBox profileComboBox; + private javax.swing.JLabel profileLabel; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; - private javax.swing.JComboBox volExecutableComboBox; - private javax.swing.JLabel volExecutableLabel; // End of variables declaration//GEN-END:variables /** * Get the path of the user selected image. @@ -325,17 +353,28 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { return pathTextField.getText(); } + /** + * + * @return Profile or empty string if auto detect + */ + String getProfile() { + String profile = (String)profileComboBox.getSelectedItem(); + if (profile.equals(AUTODETECT_PROFILE)) { + return ""; + } + return profile; + } + List getPluginsToRun() { List enabledPlugins = new ArrayList<>(); - Map pluginMap = new HashMap<>(); + Map pluginSettingsToSave = new HashMap<>(); for (String plugin : PluginListNames) { if (pluginListStates.get(plugin)) { enabledPlugins.add(plugin); - pluginMap.put(plugin, ""); } + pluginSettingsToSave.put(plugin, pluginListStates.get(plugin).toString()); } - - ModuleSettings.setConfigSettings(this.contextName, pluginMap); + ModuleSettings.setConfigSettings(this.contextName, pluginSettingsToSave); // @@ Could return keys of set return enabledPlugins; } @@ -382,7 +421,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { }) private void warnIfPathIsInvalid(String path) { try { - if (!PathValidator.isValid(path, Case.getOpenCase().getCaseType())) { + if (!PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.MemoryDSInputPanel_errorMsg_dataSourcePathOnCdrive()); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java index 9791ad1f09..dc05cb8376 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java @@ -117,7 +117,7 @@ public class MemoryDSProcessor implements DataSourceProcessor { @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { configPanel.storeSettings(); - run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback); + run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getProfile(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback); } /** @@ -131,6 +131,7 @@ public class MemoryDSProcessor implements DataSourceProcessor { * associated with the data source that is intended * to be unique across multiple cases (e.g., a UUID). * @param memoryImagePath Path to the memory image file. + * @param profile Volatility profile to run or empty string to autodetect * @param pluginsToRun The Volatility plugins to run. * @param timeZone The time zone to use when processing dates and * times for the image, obtained from @@ -139,8 +140,8 @@ public class MemoryDSProcessor implements DataSourceProcessor { * processing. * @param callback Callback to call when processing is done. */ - private void run(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, pluginsToRun, timeZone, progressMonitor, callback); + private void run(String deviceId, String memoryImagePath, String profile, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, profile, pluginsToRun, timeZone, progressMonitor, callback); new Thread(addImageTask).start(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index 0f72ebc1a8..ecec2c8359 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.modules.InstalledFileLocator; -import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -40,14 +39,14 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.EncodingType; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; /** @@ -70,6 +69,8 @@ class VolatilityProcessor { private String moduleOutputPath; private FileManager fileManager; private volatile boolean isCancelled; + private Content outputVirtDir; + private String profile; /** * Constructs a processor that runs Volatility on a given memory image file @@ -77,11 +78,13 @@ class VolatilityProcessor { * * @param memoryImagePath Path to memory image file. * @param dataSource The memory image data source. + * @param profile Volatility profile to run or empty string to autodetect * @param plugInToRuns Volatility plugins to run. * @param progressMonitor Progress monitor for reporting progress during * processing. */ - VolatilityProcessor(String memoryImagePath, Image dataSource, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) { + VolatilityProcessor(String memoryImagePath, Image dataSource, String profile, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) { + this.profile = profile; this.memoryImagePath = memoryImagePath; this.pluginsToRun = plugInToRun; this.dataSource = dataSource; @@ -105,7 +108,7 @@ class VolatilityProcessor { this.errorMsgs.clear(); try { - this.currentCase = Case.getOpenCase(); + this.currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { throw new VolatilityProcessorException(Bundle.VolatilityProcessor_progressMessage_noCurrentCase(), ex); } @@ -117,6 +120,13 @@ class VolatilityProcessor { fileManager = currentCase.getServices().getFileManager(); + try { + // make a virtual directory to store the reports + outputVirtDir = currentCase.getSleuthkitCase().addVirtualDirectory(dataSource.getId(), "ModuleOutput"); + } catch (TskCoreException ex) { + throw new VolatilityProcessorException("Error creating virtual directory", ex); + } + /* * Make an output folder unique to this data source. */ @@ -124,9 +134,14 @@ class VolatilityProcessor { moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), VOLATILITY, dataSourceId.toString()).toString(); File directory = new File(String.valueOf(moduleOutputPath)); if (!directory.exists()) { - directory.mkdirs(); + directory.mkdirs(); + } + + // if they did not specify a profile, then run imageinfo to get one + if (profile.isEmpty() ) { progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo("imageinfo")); //NON-NLS runVolatilityPlugin("imageinfo"); //NON-NLS + profile = getProfileFromImageInfoOutput(); } progressMonitor.setIndeterminate(false); @@ -136,7 +151,6 @@ class VolatilityProcessor { break; } String pluginToRun = pluginsToRun.get(i); - progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo(pluginToRun)); runVolatilityPlugin(pluginToRun); progressMonitor.setProgress(i); } @@ -172,28 +186,44 @@ class VolatilityProcessor { "VolatilityProcessor_exceptionMessage_errorIndexingOutput=Error indexing output for {0} plugin" }) private void runVolatilityPlugin(String pluginToRun) throws VolatilityProcessorException { + progressMonitor.setProgressText("Running module " + pluginToRun); + List commandLine = new ArrayList<>(); commandLine.add("\"" + executableFile + "\""); //NON-NLS File memoryImage = new File(memoryImagePath); commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS - - File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS - if (imageInfoOutputFile.exists()) { - String memoryProfile = parseImageInfoOutput(imageInfoOutputFile); - commandLine.add("--profile=" + memoryProfile); //NON-NLS + if (!profile.isEmpty()) { + commandLine.add("--profile=" + profile); //NON-NLS } - commandLine.add(pluginToRun); - String outputFile = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS + switch (pluginToRun) { + case "dlldump": + case "moddump": + case "procdump": + case "dumpregistry": + case "dumpfiles": + String outputDir = moduleOutputPath + File.separator + pluginToRun; + File directory = new File(outputDir); + if (!directory.exists()) { + directory.mkdirs(); + } + commandLine.add("--dump-dir=" + outputDir); //NON-NLS + break; + default: + break; + } + + String outputFileAsString = moduleOutputPath + File.separator + pluginToRun + ".txt"; //NON-NLS ProcessBuilder processBuilder = new ProcessBuilder(commandLine); /* * Add an environment variable to force Volatility to run with the same * permissions Autopsy uses. */ processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS - processBuilder.redirectOutput(new File(outputFile)); - processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS + File outputFile = new File(outputFileAsString); + processBuilder.redirectOutput(outputFile); + processBuilder.redirectError(new File(moduleOutputPath + File.separator + "Volatility_err.txt")); //NON-NLS processBuilder.directory(new File(memoryImage.getParent())); try { @@ -209,32 +239,16 @@ class VolatilityProcessor { if (isCancelled) { return; } - - /* - * Add the plugin output file to the case as a report. - */ + try { - Report report = currentCase.getSleuthkitCase().addReport(outputFile, VOLATILITY, VOLATILITY + " " + pluginToRun + " Plugin"); //NON-NLS - try { - KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class); - if (searchService != null) { - searchService.index(report); - } else { - errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_searchServiceNotFound(pluginToRun)); - /* - * Log the exception as well as add it to the error - * messages, to ensure that the stack trace is not lost. - */ - logger.log(Level.WARNING, Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun)); - } - } catch (TskCoreException ex) { - throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun), ex); - } + String relativePath = new File(currentCase.getCaseDirectory()).toURI().relativize(new File(outputFileAsString).toURI()).getPath(); + fileManager.addDerivedFile(pluginToRun, relativePath, outputFile.length(), 0, 0, 0, 0, true, outputVirtDir, null, null, null, null, EncodingType.NONE); } catch (TskCoreException ex) { - throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorAddingOutput(pluginToRun), ex); + errorMsgs.add("Error adding " + pluginToRun + " volatility report as a file"); + logger.log(Level.WARNING, "Error adding report as derived file", ex); } - - createArtifactsFromPluginOutput(pluginToRun, new File(outputFile)); + + createArtifactsFromPluginOutput(pluginToRun, new File(outputFileAsString)); } /** @@ -263,12 +277,18 @@ class VolatilityProcessor { @NbBundle.Messages({ "VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info" }) - private String parseImageInfoOutput(File imageOutputFile) throws VolatilityProcessorException { + private String getProfileFromImageInfoOutput() throws VolatilityProcessorException { + File imageOutputFile = new File(moduleOutputPath + File.separator + "imageinfo.txt"); //NON-NLS try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) { String fileRead = br.readLine(); - String[] profileLine = fileRead.split(":"); //NON-NLS - String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS - return memProfile[0].replaceAll("\\s+", ""); //NON-NLS + if (fileRead != null) { + String[] profileLine = fileRead.split(":"); //NON-NLS + String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS + return memProfile[0].replaceAll("\\s+", ""); //NON-NLS + } + else { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo()); + } } catch (IOException ex) { throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo(), ex); } @@ -314,6 +334,8 @@ class VolatilityProcessor { String filePath = volfile.getParent(); + logger.log(Level.INFO, "Looking up file " + fileName + " at path " + filePath); + try { List resolvedFiles; if (filePath == null) { @@ -333,12 +355,13 @@ class VolatilityProcessor { } fileName += ".%"; //NON-NLS + logger.log(Level.INFO, "Looking up file (extension wildcard) " + fileName + " at path " + filePath); + if (filePath == null) { resolvedFiles = fileManager.findFiles(fileName); //NON-NLS } else { resolvedFiles = fileManager.findFiles(fileName, filePath); //NON-NLS } - } if (resolvedFiles.isEmpty()) { @@ -387,6 +410,7 @@ class VolatilityProcessor { * @param pluginOutputFile File that contains the output to parse. */ private void createArtifactsFromPluginOutput(String pluginName, File pluginOutputFile) throws VolatilityProcessorException { + progressMonitor.setProgressText("Parsing module " + pluginName); Set fileSet = null; switch (pluginName) { case "dlllist": //NON-NLS @@ -421,6 +445,7 @@ class VolatilityProcessor { } if (fileSet != null && !fileSet.isEmpty()) { + progressMonitor.setProgressText("Flagging files from module " + pluginName); flagFiles(fileSet, pluginName); } } diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index eb907611cf..0fdad950f2 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -127,7 +127,7 @@ 10 - 10.10 + 10.11 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 5c9acf45bf..c8837fa13b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -194,7 +194,7 @@ public final class ImageGalleryController { stale.set(b); }); try { - new PerCaseProperties(Case.getOpenCase()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString()); + new PerCaseProperties(Case.getCurrentCaseThrows()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString()); } catch (NoCurrentCaseException ex) { Logger.getLogger(ImageGalleryController.class.getName()).log(Level.WARNING, "Exception while getting open case."); //NON-NLS } @@ -214,7 +214,7 @@ public final class ImageGalleryController { listeningEnabled.addListener((observable, oldValue, newValue) -> { try { //if we just turned on listening and a case is open and that case is not up to date - if (newValue && !oldValue && ImageGalleryModule.isDrawableDBStale(Case.getOpenCase())) { + if (newValue && !oldValue && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows())) { //populate the db queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase)); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java index b97a116ad8..beb2bc0a3d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java @@ -189,7 +189,7 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel { try { if (IngestManager.getInstance().isIngestRunning() == false) { enabledForCaseBox.setEnabled(true); - enabledForCaseBox.setSelected(ImageGalleryModule.isEnabledforCase(Case.getOpenCase())); + enabledForCaseBox.setSelected(ImageGalleryModule.isEnabledforCase(Case.getCurrentCaseThrows())); } else { enabledForCaseBox.setEnabled(false); enabledForCaseBox.setSelected(enabledByDefaultBox.isSelected()); @@ -204,7 +204,7 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel { void store() { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { Logger.getLogger(ImageGalleryOptionsPanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/DeleteTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/DeleteTagAction.java index 2145b76d3f..efe279bba3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/DeleteTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/DeleteTagAction.java @@ -123,7 +123,7 @@ public class DeleteTagAction extends Action { try { List existingTagsList - = Case.getOpenCase().getServices().getTagsManager() + = Case.getCurrentCaseThrows().getServices().getTagsManager() .getContentTagsByContent(file); Collection tagNamesList diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index f318b0d44c..e10a679792 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -109,7 +109,7 @@ public final class OpenAction extends CallableSystemAction { public boolean isEnabled() { Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { return false; } @@ -154,7 +154,7 @@ public final class OpenAction extends CallableSystemAction { //check case final Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; @@ -188,7 +188,7 @@ public final class OpenAction extends CallableSystemAction { private boolean tooManyFiles() { try { - return FILE_LIMIT < Case.getOpenCase().getSleuthkitCase().countFilesWhere("1 = 1"); + return FILE_LIMIT < Case.getCurrentCaseThrows().getSleuthkitCase().countFilesWhere("1 = 1"); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Can not open image gallery with no case open.", ex); } catch (TskCoreException ex) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index f62bfd1bc6..34c9d5a4c5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -75,7 +75,7 @@ public abstract class DrawableFile { } public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, NoCurrentCaseException { - return create(Case.getOpenCase().getSleuthkitCase().getAbstractFileById(id), analyzed); + return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(id), analyzed); } private SoftReference imageRef; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index 81fc907069..5f6f72279e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -91,7 +91,7 @@ public class VideoFile extends DrawableFile { if (media != null) { return media; } - final File cacheFile = VideoUtils.getTempVideoFile(this.getAbstractFile()); + final File cacheFile = VideoUtils.getVideoFileInTempDir(this.getAbstractFile()); if (cacheFile.exists() == false || cacheFile.length() < getAbstractFile().getSize()) { Files.createParentDirs(cacheFile); diff --git a/KeywordSearch/build.xml b/KeywordSearch/build.xml index 5fe021fff8..5066a8e051 100644 --- a/KeywordSearch/build.xml +++ b/KeywordSearch/build.xml @@ -29,7 +29,7 @@ - + diff --git a/KeywordSearch/manifest.mf b/KeywordSearch/manifest.mf index 60d5379544..9f3126687e 100644 --- a/KeywordSearch/manifest.mf +++ b/KeywordSearch/manifest.mf @@ -1,7 +1,7 @@ Manifest-Version: 1.0 AutoUpdate-Show-In-Client: true OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/6 -OpenIDE-Module-Implementation-Version: 18 +OpenIDE-Module-Implementation-Version: 19 OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties index 600396fb43..4af4a610bf 100644 --- a/KeywordSearch/nbproject/project.properties +++ b/KeywordSearch/nbproject/project.properties @@ -142,4 +142,4 @@ license.file=../LICENSE-2.0.txt nbm.homepage=http://www.sleuthkit.org/autopsy/ nbm.needs.restart=true source.reference.commons-validator-1.5.1.jar=release/modules/ext/commons-validator-1.5.1-sources.jar -spec.version.base=6.4 +spec.version.base=6.5 diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index a8b49012cf..25142d8119 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -119,7 +119,7 @@ 10 - 10.10 + 10.11 diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java similarity index 93% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java index 739fe787e8..b3a3ec463d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java @@ -23,7 +23,7 @@ import java.util.Map; /** * Stores data about a search before it is done. */ -final class QueryRequest { +final class AdHocQueryRequest { private final KeywordSearchQuery query; private final String queryString; @@ -36,7 +36,7 @@ final class QueryRequest { * @param id ID that callers simply increment from 0 * @param query Query that will be performed. */ - QueryRequest(Map map, int id, KeywordSearchQuery query) { + AdHocQueryRequest(Map map, int id, KeywordSearchQuery query) { this.queryString = query.getEscapedQueryString(); this.queryProperties = map; this.query = query; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java similarity index 91% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index 290fe8b086..ced485d9c6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -48,7 +48,7 @@ import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.datamodel.KeyValue; import org.sleuthkit.autopsy.datamodel.KeyValueNode; -import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent; +import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.KeywordHitKey; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -66,9 +66,9 @@ import org.sleuthkit.datamodel.TskCoreException; * Responsible for assembling nodes and columns in the right way and performing * lazy queries as needed. */ -class KeywordSearchResultFactory extends ChildFactory { +class AdHocSearchChildFactory extends ChildFactory { - private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName()); + private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName()); //common properties (superset of all Node properties) to be displayed as columns static final List COMMON_PROPERTIES @@ -82,9 +82,9 @@ class KeywordSearchResultFactory extends ChildFactory { .map(Object::toString)) .collect(Collectors.toList()); - private final Collection queryRequests; + private final Collection queryRequests; - KeywordSearchResultFactory(Collection queryRequests) { + AdHocSearchChildFactory(Collection queryRequests) { this.queryRequests = queryRequests; } @@ -98,7 +98,7 @@ class KeywordSearchResultFactory extends ChildFactory { @Override protected boolean createKeys(List toPopulate) { - for (QueryRequest queryRequest : queryRequests) { + for (AdHocQueryRequest queryRequest : queryRequests) { /** * Check the validity of the requested query. */ @@ -148,14 +148,14 @@ class KeywordSearchResultFactory extends ChildFactory { } SleuthkitCase tskCase; try { - tskCase = Case.getOpenCase().getSleuthkitCase(); + tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS return false; } int hitNumber = 0; - List tempList = new ArrayList<>(); + List tempList = new ArrayList<>(); for (KeywordHit hit : getOneHitPerObject(queryResults)) { /** @@ -203,7 +203,7 @@ class KeywordSearchResultFactory extends ChildFactory { hitName = contentName; } hitNumber++; - tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults)); + tempList.add(new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults)); } @@ -253,8 +253,8 @@ class KeywordSearchResultFactory extends ChildFactory { protected Node createNodeForKey(KeyValue key) { Node resultNode; - if (key instanceof KeyValueQueryContent) { - AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key); + if (key instanceof KeywordHitKey) { + AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeywordHitKey) key); /** * Place the Content, Artifact and hit results into the lookup for @@ -262,17 +262,17 @@ class KeywordSearchResultFactory extends ChildFactory { */ ArrayList lookups = new ArrayList<>(); lookups.add(adHocQueryResult); - if (((KeyValueQueryContent) key).getContent() != null) { - lookups.add(((KeyValueQueryContent) key).getContent()); + if (((KeywordHitKey) key).getContent() != null) { + lookups.add(((KeywordHitKey) key).getContent()); } - if (((KeyValueQueryContent) key).getArtifact() != null) { - lookups.add(((KeyValueQueryContent) key).getArtifact()); + if (((KeywordHitKey) key).getArtifact() != null) { + lookups.add(((KeywordHitKey) key).getArtifact()); } Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray())); //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization - resultNode = new KeywordSearchFilterNode(kvNode); + resultNode = new AdHocSearchFilterNode(kvNode); } else { resultNode = new EmptyNode("This Node Is Empty"); resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text")); @@ -298,7 +298,7 @@ class KeywordSearchResultFactory extends ChildFactory { * which the hit was found. * @param results The query results. */ - AdHocQueryResult(KeyValueQueryContent key) { + AdHocQueryResult(KeywordHitKey key) { this.solrObjectId = key.getSolrObjectId(); this.results = key.getHits(); } @@ -327,7 +327,7 @@ class KeywordSearchResultFactory extends ChildFactory { * Used to display keyword search results in table. Eventually turned into a * node. */ - class KeyValueQueryContent extends KeyValue { + class KeywordHitKey extends KeyValue { private final long solrObjectId; @@ -350,7 +350,7 @@ class KeywordSearchResultFactory extends ChildFactory { * @param query Query used in search * @param hits Full set of search results (for all files! @@@) */ - KeyValueQueryContent(String name, Map map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) { + KeywordHitKey(String name, Map map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) { super(name, map, id); this.solrObjectId = solrObjectId; this.content = content; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java similarity index 89% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java index f2dfb53622..9afb18e100 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java @@ -37,14 +37,14 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Delegates the actual work to the various implementations of * KeywordSearchQuery. */ -class KeywordSearchQueryDelegator { +class AdHocSearchDelegator { - private List keywordLists; + private final List keywordLists; private List queryDelegates; private static int resultWindowCount = 0; //keep track of unique window ids to display - private static Logger logger = Logger.getLogger(KeywordSearchQueryDelegator.class.getName()); + private static final Logger logger = Logger.getLogger(AdHocSearchDelegator.class.getName()); - public KeywordSearchQueryDelegator(List keywordLists) { + public AdHocSearchDelegator(List keywordLists) { this.keywordLists = keywordLists; init(); } @@ -66,14 +66,14 @@ class KeywordSearchQueryDelegator { * Post results into a new DataResultViewer. */ public void execute() { - Collection queryRequests = new ArrayList<>(); + Collection queryRequests = new ArrayList<>(); int queryID = 0; StringBuilder queryConcat = new StringBuilder(); // concatenation of all query strings for (KeywordSearchQuery q : queryDelegates) { Map kvs = new LinkedHashMap<>(); final String queryStr = q.getQueryString(); queryConcat.append(queryStr).append(" "); - queryRequests.add(new QueryRequest(kvs, ++queryID, q)); + queryRequests.add(new AdHocQueryRequest(kvs, ++queryID, q)); } String queryConcatStr = queryConcat.toString(); @@ -85,7 +85,7 @@ class KeywordSearchQueryDelegator { Node rootNode; if (queryRequests.size() > 0) { Children childNodes = - Children.create(new KeywordSearchResultFactory(queryRequests), true); + Children.create(new AdHocSearchChildFactory(queryRequests), true); rootNode = new AbstractNode(childNodes); } else { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java similarity index 95% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index 6dc49ff484..3177bbbbb4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -53,15 +53,17 @@ import org.sleuthkit.datamodel.VirtualDirectory; /** * FilterNode containing properties and actions for keyword search. + * + * Wraps the generic KeyValue node and customizes the property sheet and lookup */ -class KeywordSearchFilterNode extends FilterNode { +class AdHocSearchFilterNode extends FilterNode { /** * Instantiate a KeywordSearchFilterNode. * * @param original The original source node. */ - KeywordSearchFilterNode(Node original) { + AdHocSearchFilterNode(Node original) { super(original, null, new ProxyLookup(original.getLookup())); } @@ -116,7 +118,7 @@ class KeywordSearchFilterNode extends FilterNode { @Override public List visit(Report r) { List actionsList = new ArrayList<>(); - actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), KeywordSearchFilterNode.this)); + actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; @@ -161,7 +163,7 @@ class KeywordSearchFilterNode extends FilterNode { private List getFileActions(boolean enableHashSearch) { List actionsList = new ArrayList<>(); - actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), KeywordSearchFilterNode.this)); + actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); actionsList.add(null); actionsList.add(ExtractAction.getInstance()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java similarity index 96% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 1e74bceefe..13798614c5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -30,12 +30,12 @@ import org.openide.util.NbBundle; * class and extended classes model the user's intentions, not necessarily how * the search manager and 3rd party tools actually perform the search. */ -abstract class KeywordSearchPanel extends javax.swing.JPanel { +abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); protected int filesIndexed; - KeywordSearchPanel() { + AdHocSearchPanel() { initListeners(); } @@ -110,7 +110,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel { } } - KeywordSearchQueryDelegator man = null; + AdHocSearchDelegator man = null; final List keywordLists = getKeywordLists(); if (keywordLists.isEmpty()) { @@ -119,7 +119,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); return; } - man = new KeywordSearchQueryDelegator(keywordLists); + man = new AdHocSearchDelegator(keywordLists); if (man.validate()) { man.execute(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ArtifactTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ArtifactTextExtractor.java index 61b9bb3b20..a2724e72da 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ArtifactTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ArtifactTextExtractor.java @@ -58,9 +58,9 @@ class ArtifactTextExtractor implements TextExtractor { Case currentCase; try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ignore) { - // thorown by Case.getOpenCase() if currentCase is null + // thorown by Case.getCurrentOpenCase() if currentCase is null return null; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java index 40cc83cd92..89e59cd2eb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java @@ -44,7 +44,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager; * Viewer panel widget for keyword lists that is used in the ingest config and * options area. */ -class DropdownListSearchPanel extends KeywordSearchPanel { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +class DropdownListSearchPanel extends AdHocSearchPanel { private static final Logger logger = Logger.getLogger(DropdownListSearchPanel.class.getName()); private static DropdownListSearchPanel instance; @@ -131,7 +132,7 @@ class DropdownListSearchPanel extends KeywordSearchPanel { @Override public void actionPerformed(ActionEvent e) { if (ingestRunning) { - SearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists()); + IngestSearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists()); logger.log(Level.INFO, "Submitted enqueued lists to ingest"); //NON-NLS } else { searchAction(e); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index 512d62238a..8dd9a20b0b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.swing.JMenuItem; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; /** @@ -42,7 +43,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; * perform this task at the desired size, and neither could numerous other * fonts. */ -public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName()); @@ -135,8 +137,22 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { * * @return The keyword list. */ + @NbBundle.Messages({"DropdownSingleTermSearchPanel.warning.title=Warning", + "DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"}) @Override List getKeywordLists() { + + if (regexRadioButton.isSelected()) { + if((keywordTextField.getText() != null) && + (keywordTextField.getText().startsWith("^") || + (keywordTextField.getText().endsWith("$") && ! keywordTextField.getText().endsWith("\\$")))) { + + KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "DropdownSingleTermSearchPanel.warning.title"), + NbBundle.getMessage(this.getClass(), "DropdownSingleTermSearchPanel.warning.text"), + KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN); + } + } + List keywords = new ArrayList<>(); keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected())); List keywordLists = new ArrayList<>(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form index 102d8a35cf..86e0672029 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form @@ -27,7 +27,7 @@ - + @@ -45,74 +45,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - + @@ -123,7 +65,6 @@ - @@ -143,256 +84,358 @@ - + - - - - + - - - - - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index bc68962693..f7463bbccb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -145,133 +145,140 @@ class ExtractedContentPanel extends javax.swing.JPanel { copyMenuItem = new javax.swing.JMenuItem(); selectAllMenuItem = new javax.swing.JMenuItem(); jScrollPane1 = new javax.swing.JScrollPane(); - extractedTextPane = new JTextPane(){ - public boolean getScrollableTracksViewportWidth() { - return (getSize().width < 400); - }}; - sourceComboBox = new javax.swing.JComboBox<>(); - jLabel1 = new javax.swing.JLabel(); - pageOfLabel = new javax.swing.JLabel(); - pageButtonsLabel = new javax.swing.JLabel(); - pageTotalLabel = new javax.swing.JLabel(); - pagesLabel = new javax.swing.JLabel(); - pageNextButton = new javax.swing.JButton(); - pagePreviousButton = new javax.swing.JButton(); - pageCurLabel = new javax.swing.JLabel(); - jSeparator1 = new javax.swing.JSeparator(); - hitLabel = new javax.swing.JLabel(); - hitButtonsLabel = new javax.swing.JLabel(); - hitNextButton = new javax.swing.JButton(); - hitOfLabel = new javax.swing.JLabel(); - hitTotalLabel = new javax.swing.JLabel(); - hitPreviousButton = new javax.swing.JButton(); - hitCountLabel = new javax.swing.JLabel(); - jSeparator2 = new javax.swing.JSeparator(); + extractedTextPane = new javax.swing.JTextPane(); + jScrollPane2 = new javax.swing.JScrollPane(); + jPanel1 = new javax.swing.JPanel(); + sourceComboBox = new javax.swing.JComboBox<>(); + jLabel1 = new javax.swing.JLabel(); + pageOfLabel = new javax.swing.JLabel(); + pageButtonsLabel = new javax.swing.JLabel(); + pageTotalLabel = new javax.swing.JLabel(); + pagesLabel = new javax.swing.JLabel(); + pageNextButton = new javax.swing.JButton(); + pagePreviousButton = new javax.swing.JButton(); + pageCurLabel = new javax.swing.JLabel(); + jSeparator1 = new javax.swing.JSeparator(); + hitLabel = new javax.swing.JLabel(); + hitButtonsLabel = new javax.swing.JLabel(); + hitNextButton = new javax.swing.JButton(); + hitOfLabel = new javax.swing.JLabel(); + hitTotalLabel = new javax.swing.JLabel(); + hitPreviousButton = new javax.swing.JButton(); + hitCountLabel = new javax.swing.JLabel(); + jSeparator2 = new javax.swing.JSeparator(); - copyMenuItem.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.copyMenuItem.text")); // NOI18N - rightClickMenu.add(copyMenuItem); + copyMenuItem.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.copyMenuItem.text")); // NOI18N + rightClickMenu.add(copyMenuItem); - selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.selectAllMenuItem.text")); // NOI18N - rightClickMenu.add(selectAllMenuItem); + selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.selectAllMenuItem.text")); // NOI18N + rightClickMenu.add(selectAllMenuItem); - setPreferredSize(new java.awt.Dimension(717, 58)); + setPreferredSize(new java.awt.Dimension(100, 58)); - jScrollPane1.setBackground(new java.awt.Color(255, 255, 255)); - jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - jScrollPane1.setPreferredSize(new java.awt.Dimension(640, 29)); + jScrollPane1.setBackground(new java.awt.Color(255, 255, 255)); + jScrollPane1.setPreferredSize(new java.awt.Dimension(640, 29)); - extractedTextPane.setEditable(false); - extractedTextPane.setAutoscrolls(false); - extractedTextPane.setInheritsPopupMenu(true); - extractedTextPane.setMaximumSize(new java.awt.Dimension(2000, 2000)); - extractedTextPane.setPreferredSize(new java.awt.Dimension(640, 29)); - jScrollPane1.setViewportView(extractedTextPane); + extractedTextPane.setEditable(false); + extractedTextPane.setAutoscrolls(false); + extractedTextPane.setInheritsPopupMenu(true); + extractedTextPane.setMaximumSize(new java.awt.Dimension(2000, 2000)); + extractedTextPane.setPreferredSize(new java.awt.Dimension(600, 29)); + jScrollPane1.setViewportView(extractedTextPane); - sourceComboBox.setModel(new javax.swing.DefaultComboBoxModel()); - sourceComboBox.setMaximumSize(new java.awt.Dimension(150, 32767)); - sourceComboBox.setMinimumSize(new java.awt.Dimension(150, 20)); - sourceComboBox.setPreferredSize(new java.awt.Dimension(150, 20)); + jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane2.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + jScrollPane2.setPreferredSize(new java.awt.Dimension(600, 100)); - jLabel1.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.jLabel1.text")); // NOI18N + jPanel1.setMinimumSize(new java.awt.Dimension(0, 0)); + jPanel1.setPreferredSize(new java.awt.Dimension(600, 81)); - pageOfLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageOfLabel.text")); // NOI18N + sourceComboBox.setModel(new javax.swing.DefaultComboBoxModel()); + sourceComboBox.setMaximumSize(new java.awt.Dimension(150, 32767)); + sourceComboBox.setMinimumSize(new java.awt.Dimension(150, 20)); + sourceComboBox.setPreferredSize(new java.awt.Dimension(150, 20)); - pageButtonsLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageButtonsLabel.text")); // NOI18N + jLabel1.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.jLabel1.text")); // NOI18N - pageTotalLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - pageTotalLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageTotalLabel.text")); // NOI18N + pageOfLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageOfLabel.text")); // NOI18N - pagesLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pagesLabel.text")); // NOI18N + pageButtonsLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageButtonsLabel.text")); // NOI18N - pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward.png"))); // NOI18N - pageNextButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageNextButton.text")); // NOI18N - pageNextButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); - pageNextButton.setBorderPainted(false); - pageNextButton.setContentAreaFilled(false); - pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward_disabled.png"))); // NOI18N - pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); - pageNextButton.setPreferredSize(new java.awt.Dimension(23, 23)); + pageTotalLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + pageTotalLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageTotalLabel.text")); // NOI18N - pagePreviousButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back.png"))); // NOI18N - pagePreviousButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pagePreviousButton.text")); // NOI18N - pagePreviousButton.setActionCommand(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pagePreviousButton.actionCommand")); // NOI18N - pagePreviousButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); - pagePreviousButton.setBorderPainted(false); - pagePreviousButton.setContentAreaFilled(false); - pagePreviousButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back_disabled.png"))); // NOI18N - pagePreviousButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + pagesLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pagesLabel.text")); // NOI18N - pageCurLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - pageCurLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageCurLabel.text")); // NOI18N + pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward.png"))); // NOI18N + pageNextButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageNextButton.text")); // NOI18N + pageNextButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); + pageNextButton.setBorderPainted(false); + pageNextButton.setContentAreaFilled(false); + pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward_disabled.png"))); // NOI18N + pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + pageNextButton.setPreferredSize(new java.awt.Dimension(23, 23)); - jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); + pagePreviousButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back.png"))); // NOI18N + pagePreviousButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pagePreviousButton.text")); // NOI18N + pagePreviousButton.setActionCommand(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pagePreviousButton.actionCommand")); // NOI18N + pagePreviousButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); + pagePreviousButton.setBorderPainted(false); + pagePreviousButton.setContentAreaFilled(false); + pagePreviousButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back_disabled.png"))); // NOI18N + pagePreviousButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); - hitLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitLabel.text")); // NOI18N - hitLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitLabel.toolTipText")); // NOI18N + pageCurLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + pageCurLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageCurLabel.text")); // NOI18N - hitButtonsLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitButtonsLabel.text")); // NOI18N + jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); - hitNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward.png"))); // NOI18N - hitNextButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitNextButton.text")); // NOI18N - hitNextButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); - hitNextButton.setBorderPainted(false); - hitNextButton.setContentAreaFilled(false); - hitNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward_disabled.png"))); // NOI18N - hitNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); - hitNextButton.setPreferredSize(new java.awt.Dimension(23, 23)); - hitNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward_hover.png"))); // NOI18N + hitLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitLabel.text")); // NOI18N + hitLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitLabel.toolTipText")); // NOI18N - hitOfLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitOfLabel.text")); // NOI18N + hitButtonsLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitButtonsLabel.text")); // NOI18N - hitTotalLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - hitTotalLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitTotalLabel.text")); // NOI18N - hitTotalLabel.setMaximumSize(new java.awt.Dimension(18, 14)); - hitTotalLabel.setMinimumSize(new java.awt.Dimension(18, 14)); - hitTotalLabel.setPreferredSize(new java.awt.Dimension(18, 14)); + hitNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward.png"))); // NOI18N + hitNextButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitNextButton.text")); // NOI18N + hitNextButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); + hitNextButton.setBorderPainted(false); + hitNextButton.setContentAreaFilled(false); + hitNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward_disabled.png"))); // NOI18N + hitNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + hitNextButton.setPreferredSize(new java.awt.Dimension(23, 23)); + hitNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_forward_hover.png"))); // NOI18N - hitPreviousButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back.png"))); // NOI18N - hitPreviousButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitPreviousButton.text")); // NOI18N - hitPreviousButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); - hitPreviousButton.setBorderPainted(false); - hitPreviousButton.setContentAreaFilled(false); - hitPreviousButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back_disabled.png"))); // NOI18N - hitPreviousButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); - hitPreviousButton.setPreferredSize(new java.awt.Dimension(23, 23)); - hitPreviousButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back_hover.png"))); // NOI18N + hitOfLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitOfLabel.text")); // NOI18N - hitCountLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - hitCountLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitCountLabel.text")); // NOI18N - hitCountLabel.setMaximumSize(new java.awt.Dimension(18, 14)); - hitCountLabel.setMinimumSize(new java.awt.Dimension(18, 14)); - hitCountLabel.setPreferredSize(new java.awt.Dimension(18, 14)); + hitTotalLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + hitTotalLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitTotalLabel.text")); // NOI18N + hitTotalLabel.setMaximumSize(new java.awt.Dimension(18, 14)); + hitTotalLabel.setMinimumSize(new java.awt.Dimension(18, 14)); + hitTotalLabel.setPreferredSize(new java.awt.Dimension(18, 14)); - jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL); + hitPreviousButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back.png"))); // NOI18N + hitPreviousButton.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitPreviousButton.text")); // NOI18N + hitPreviousButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); + hitPreviousButton.setBorderPainted(false); + hitPreviousButton.setContentAreaFilled(false); + hitPreviousButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back_disabled.png"))); // NOI18N + hitPreviousButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + hitPreviousButton.setPreferredSize(new java.awt.Dimension(23, 23)); + hitPreviousButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/btn_step_back_hover.png"))); // NOI18N - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() + hitCountLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + hitCountLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitCountLabel.text")); // NOI18N + hitCountLabel.setMaximumSize(new java.awt.Dimension(18, 14)); + hitCountLabel.setMinimumSize(new java.awt.Dimension(18, 14)); + hitCountLabel.setPreferredSize(new java.awt.Dimension(18, 14)); + + jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 600, Short.MAX_VALUE) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() .addComponent(hitLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) @@ -308,14 +315,15 @@ class ExtractedContentPanel extends javax.swing.JPanel { .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(sourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap()) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() + .addContainerGap())) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 81, Short.MAX_VALUE) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(hitPreviousButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel1) .addComponent(hitNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -334,13 +342,28 @@ class ExtractedContentPanel extends javax.swing.JPanel { .addComponent(hitOfLabel) .addComponent(hitTotalLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(hitButtonsLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE)) - ); + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + ); - layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {hitButtonsLabel, hitCountLabel, hitLabel, hitNextButton, hitOfLabel, hitPreviousButton, hitTotalLabel, jLabel1, jSeparator1, jSeparator2, pageButtonsLabel, pageCurLabel, pageNextButton, pageOfLabel, pagePreviousButton, pageTotalLabel, pagesLabel, sourceComboBox}); + jPanel1Layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {hitButtonsLabel, hitCountLabel, hitLabel, hitNextButton, hitOfLabel, hitPreviousButton, hitTotalLabel, jLabel1, jSeparator1, jSeparator2, pageButtonsLabel, pageCurLabel, pageNextButton, pageOfLabel, pagePreviousButton, pageTotalLabel, pagesLabel, sourceComboBox}); - }// //GEN-END:initComponents + jScrollPane2.setViewportView(jPanel1); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 23, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JMenuItem copyMenuItem; private javax.swing.JTextPane extractedTextPane; @@ -352,7 +375,9 @@ class ExtractedContentPanel extends javax.swing.JPanel { private javax.swing.JButton hitPreviousButton; private javax.swing.JLabel hitTotalLabel; private javax.swing.JLabel jLabel1; + private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; private javax.swing.JSeparator jSeparator1; private javax.swing.JSeparator jSeparator2; private javax.swing.JLabel pageButtonsLabel; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 62a1837e4d..da4c49def1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult; +import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.AdHocQueryResult; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -220,7 +220,7 @@ public class ExtractedContentViewer implements DataContentViewer { BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE); if (attribute != null) { long artifactId = attribute.getValueLong(); - BlackboardArtifact associatedArtifact = Case.getOpenCase().getSleuthkitCase().getBlackboardArtifact(artifactId); + BlackboardArtifact associatedArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(artifactId); rawArtifactText = new RawText(associatedArtifact, associatedArtifact.getArtifactID()); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java index 1b55a2926c..da7cf3c459 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java @@ -125,7 +125,10 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis } } - @NbBundle.Messages("GlobalEditListPanel.editKeyword.title=Edit Keyword") + @NbBundle.Messages({"GlobalEditListPanel.editKeyword.title=Edit Keyword", + "GlobalEditListPanel.warning.title=Warning", + "GlobalEditListPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"}) + /** * Adds keywords to a keyword list, returns true if at least one keyword was * successfully added and no duplicates were found. @@ -151,6 +154,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis dupeCount = 0; badCount = 0; keywordsToRedisplay = ""; + boolean displayedBoundaryWarning = false; if (!dialog.getKeywords().isEmpty()) { @@ -164,6 +168,19 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis dupeCount++; continue; } + + // Check if it is a regex and starts or ends with a boundary character + if (( ! displayedBoundaryWarning) && dialog.isKeywordRegex()) { + if(newWord.startsWith("^") || + (newWord.endsWith("$") && ! newWord.endsWith("\\$"))) { + + KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.title"), + NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.text"), + KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN); + // Only display the warning once + displayedBoundaryWarning = true; + } + } //check if valid boolean valid = true; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java similarity index 96% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java index 396e95dc0d..70fd3ba370 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java @@ -55,10 +55,10 @@ import org.sleuthkit.autopsy.ingest.IngestServices; * Singleton keyword search manager: Launches search threads for each job and * performs commits, both on timed intervals. */ -final class SearchRunner { +final class IngestSearchRunner { - private static final Logger logger = Logger.getLogger(SearchRunner.class.getName()); - private static SearchRunner instance = null; + private static final Logger logger = Logger.getLogger(IngestSearchRunner.class.getName()); + private static IngestSearchRunner instance = null; private IngestServices services = IngestServices.getInstance(); private Ingester ingester = null; private long currentUpdateIntervalMs; @@ -71,7 +71,7 @@ final class SearchRunner { // maps a jobID to the search private Map jobs = new ConcurrentHashMap<>(); - SearchRunner() { + IngestSearchRunner() { currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000; ingester = Ingester.getDefault(); jobProcessingExecutor = new ScheduledThreadPoolExecutor(NUM_SEARCH_SCHEDULING_THREADS, new ThreadFactoryBuilder().setNameFormat(SEARCH_SCHEDULER_THREAD_NAME).build()); @@ -81,9 +81,9 @@ final class SearchRunner { * * @return the singleton object */ - public static synchronized SearchRunner getInstance() { + public static synchronized IngestSearchRunner getInstance() { if (instance == null) { - instance = new SearchRunner(); + instance = new IngestSearchRunner(); } return instance; } @@ -167,7 +167,7 @@ final class SearchRunner { } //stop currentSearcher - SearchRunner.Searcher currentSearcher = job.getCurrentSearcher(); + IngestSearchRunner.Searcher currentSearcher = job.getCurrentSearcher(); if ((currentSearcher != null) && (!currentSearcher.isDone())) { logger.log(Level.INFO, "Cancelling search job {0}", jobId); //NON-NLS currentSearcher.cancel(true); @@ -229,7 +229,7 @@ final class SearchRunner { logger.log(Level.INFO, "Checking for previous search for search job {0} before executing final search", job.getJobId()); //NON-NLS job.waitForCurrentWorker(); - SearchRunner.Searcher finalSearcher = new SearchRunner.Searcher(job, true); + IngestSearchRunner.Searcher finalSearcher = new IngestSearchRunner.Searcher(job, true); job.setCurrentSearcher(finalSearcher); //save the ref logger.log(Level.INFO, "Kicking off final search for search job {0}", job.getJobId()); //NON-NLS finalSearcher.execute(); //start thread @@ -252,7 +252,7 @@ final class SearchRunner { */ private final class PeriodicSearchTask implements Runnable { - private final Logger logger = Logger.getLogger(SearchRunner.PeriodicSearchTask.class.getName()); + private final Logger logger = Logger.getLogger(IngestSearchRunner.PeriodicSearchTask.class.getName()); @Override public void run() { @@ -341,7 +341,7 @@ final class SearchRunner { // Map of keyword to the object ids that contain a hit private Map> currentResults; //guarded by SearchJobInfo.this - private SearchRunner.Searcher currentSearcher; + private IngestSearchRunner.Searcher currentSearcher; private AtomicLong moduleReferenceCount = new AtomicLong(0); private final Object finalSearchLock = new Object(); //used for a condition wait @@ -393,11 +393,11 @@ final class SearchRunner { workerRunning = flag; } - private synchronized SearchRunner.Searcher getCurrentSearcher() { + private synchronized IngestSearchRunner.Searcher getCurrentSearcher() { return currentSearcher; } - private synchronized void setCurrentSearcher(SearchRunner.Searcher searchRunner) { + private synchronized void setCurrentSearcher(IngestSearchRunner.Searcher searchRunner) { currentSearcher = searchRunner; } @@ -453,7 +453,7 @@ final class SearchRunner { private List keywordLists; private Map keywordToList; //keyword to list name mapping private AggregateProgressHandle progressGroup; - private final Logger logger = Logger.getLogger(SearchRunner.Searcher.class.getName()); + private final Logger logger = Logger.getLogger(IngestSearchRunner.Searcher.class.getName()); private boolean finalRun = false; Searcher(SearchJobInfo job) { @@ -483,7 +483,7 @@ final class SearchRunner { if (progressGroup != null) { progressGroup.setDisplayName(displayName + " " + NbBundle.getMessage(this.getClass(), "SearchRunner.doInBackGround.cancelMsg")); } - return SearchRunner.Searcher.this.cancel(true); + return IngestSearchRunner.Searcher.this.cancel(true); } }, null); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index c7f4c07f6a..b9c4541c7b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,6 +27,8 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; import org.sleuthkit.datamodel.AbstractFile; @@ -235,7 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); + EnterpriseHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java index c45b70c863..c147618558 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java @@ -119,7 +119,7 @@ class KeywordHit implements Comparable { // If the hit was in an artifact, look up the source content for the artifact. SleuthkitCase caseDb; try { - caseDb = Case.getOpenCase().getSleuthkitCase(); + caseDb = Case.getCurrentCaseThrows().getSleuthkitCase(); } catch (NoCurrentCaseException ex) { throw new TskCoreException("Exception while getting open case.", ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 83cdafb88a..83b770bc58 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -184,7 +184,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { // if first instance of this module for this job then check the server and existence of keywords Case openCase; try { - openCase = Case.getOpenCase(); + openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.KeywordSearchIngestModule_noOpenCase_errMsg(), ex); } @@ -288,7 +288,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { return ProcessResult.OK; } List keywordListNames = settings.getNamesOfEnabledKeyWordLists(); - SearchRunner.getInstance().startJob(context, keywordListNames); + IngestSearchRunner.getInstance().startJob(context, keywordListNames); startedSearching = true; } @@ -309,13 +309,13 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (context.fileIngestIsCancelled()) { logger.log(Level.INFO, "Keyword search ingest module instance {0} stopping search job due to ingest cancellation", instanceNum); //NON-NLS - SearchRunner.getInstance().stopJob(jobId); + IngestSearchRunner.getInstance().stopJob(jobId); cleanup(); return; } // Remove from the search list and trigger final commit and final search - SearchRunner.getInstance().endJob(jobId); + IngestSearchRunner.getInstance().endJob(jobId); // We only need to post the summary msg from the last module per job if (refCounter.decrementAndGet(jobId) == 0) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java index b4e07d0d8a..e27a01b063 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java @@ -213,7 +213,7 @@ class QueryResults { */ Content content = null; try { - SleuthkitCase tskCase = Case.getOpenCase().getSleuthkitCase(); + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); content = tskCase.getContentById(hit.getContentID()); } catch (TskCoreException | NoCurrentCaseException tskCoreException) { logger.log(Level.SEVERE, "Failed to get text source object for ", tskCoreException); //NON-NLS diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 1ddd06680e..cb640e20bd 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -170,10 +170,13 @@ final class RegexQuery implements KeywordSearchQuery { */ // We construct the query by surrounding it with slashes (to indicate it is // a regular expression search) and .* as anchors (if the query doesn't - // already have them). + // already have them). We do not add .* if there is a boundary character. + boolean skipWildcardPrefix = queryStringContainsWildcardPrefix || getQueryString().startsWith("^"); + boolean skipWildcardSuffix = queryStringContainsWildcardSuffix || + (getQueryString().endsWith("$") && ( ! getQueryString().endsWith("\\$"))); solrQuery.setQuery((field == null ? Server.Schema.CONTENT_STR.toString() : field) + ":/" - + (queryStringContainsWildcardPrefix ? "" : ".*") + getQueryString() - + (queryStringContainsWildcardSuffix ? "" : ".*") + "/"); + + (skipWildcardPrefix ? "" : ".*") + getQueryString() + + (skipWildcardSuffix ? "" : ".*") + "/"); // Set the fields we want to have returned by the query. solrQuery.setFields(Server.Schema.CONTENT_STR.toString(), Server.Schema.ID.toString(), Server.Schema.CHUNK_SIZE.toString()); @@ -591,7 +594,7 @@ final class RegexQuery implements KeywordSearchQuery { * Create an account instance. */ try { - AccountFileInstance ccAccountInstance = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString() , MODULE_NAME, content); + AccountFileInstance ccAccountInstance = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString() , MODULE_NAME, content); ccAccountInstance.addAttributes(attributes); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 416f2b394f..23f82c25e9 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,6 +70,8 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -708,7 +710,9 @@ public class Server { if (null == currentCore) { throw new NoOpenCoreException(); } + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); currentCore.addDocument(doc); + EnterpriseHealthMonitor.submitTimingMetric(metric); } finally { currentCoreLock.readLock().unlock(); } @@ -773,7 +777,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); + EnterpriseHealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); @@ -1315,11 +1321,13 @@ public class Server { * @throws IOException */ void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException { + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); CoreAdminRequest statusRequest = new CoreAdminRequest(); statusRequest.setCoreName( null ); statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS ); statusRequest.setIndexInfoNeeded(false); statusRequest.process(solrServer); + EnterpriseHealthMonitor.submitTimingMetric(metric); } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 9f86212ec6..9a642e0df1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,63 +71,68 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName()); /** - * Adds an artifact to the keyword search text index as a concantenation of - * all of its attributes. + * Indexes the given content for keyword search. * - * @param artifact The artifact to index. + * IMPORTANT: Currently, there are two correct uses for this code: * - * @throws org.sleuthkit.datamodel.TskCoreException - */ - @Override - public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException { - if (artifact == null) { - return; - } - - // We only support artifact indexing for Autopsy versions that use - // the negative range for artifact ids. - if (artifact.getArtifactID() > 0) { - return; - } - final Ingester ingester = Ingester.getDefault(); - - try { - ingester.indexMetaDataOnly(artifact); - ingester.indexText(new ArtifactTextExtractor(), artifact, null); - } catch (Ingester.IngesterException ex) { - throw new TskCoreException(ex.getCause().getMessage(), ex); - } - } - - /** - * Add the given Content object to the text index. + * 1) Indexing an artifact created during while either the file level ingest + * module pipeline or the first stage data source level ingest module + * pipeline of an ingest job is running. + * + * 2) Indexing a report. * * @param content The content to index. * - * @throws TskCoreException + * @throws TskCoreException If there is a problem indexing the content. */ @Override public void index(Content content) throws TskCoreException { + /* + * TODO (JIRA-1099): The following code has some issues that need to be + * resolved. For artifacts, it is assumed that the posting of artifacts + * is only occuring during an ingest job with an enabled keyword search + * ingest module handling index commits; it also assumes that the + * artifacts are only posted by modules in the either the file level + * ingest pipeline or the first stage data source level ingest pipeline, + * so that the artifacts will be searched during a periodic or final + * keyword search. It also assumes that the only other type of Content + * for which this API will be called are Reports generated at a time + * when doing a commit is required and desirable, i.e., in a context + * other than an ingest job. + */ if (content == null) { return; } final Ingester ingester = Ingester.getDefault(); - - try { - ingester.indexText(new TikaTextExtractor(), content, null); - } catch (Ingester.IngesterException ex) { - try { - // Try the StringsTextExtractor if Tika extractions fails. - ingester.indexText(new StringsTextExtractor(), content, null); - } catch (Ingester.IngesterException ex1) { - throw new TskCoreException(ex.getCause().getMessage(), ex1); + if (content instanceof BlackboardArtifact) { + BlackboardArtifact artifact = (BlackboardArtifact) content; + if (artifact.getArtifactID() > 0) { + /* + * Artifact indexing is only supported for artifacts that use + * negative artifact ids to avoid overlapping with the object + * ids of other types of Content. + */ + return; } + try { + ingester.indexMetaDataOnly(artifact); + ingester.indexText(new ArtifactTextExtractor(), artifact, null); + } catch (Ingester.IngesterException ex) { + throw new TskCoreException(ex.getCause().getMessage(), ex); + } + } else { + try { + ingester.indexText(new TikaTextExtractor(), content, null); + } catch (Ingester.IngesterException ex) { + try { + // Try the StringsTextExtractor if Tika extractions fails. + ingester.indexText(new StringsTextExtractor(), content, null); + } catch (Ingester.IngesterException ex1) { + throw new TskCoreException(ex.getCause().getMessage(), ex1); + } + } + ingester.commit(); } - - // TODO: Review whether this is the right thing to do. We typically use - // a combination of autoCommit and the SearchRunner to ensure that data - // is committed but that might not be sufficient for reports (or artifacts). - ingester.commit(); } /** @@ -402,7 +407,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { * in less than roughly two seconds. This stuff should be reworked using * an ExecutorService and tasks with Futures. */ - KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters(); + AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters(); try { Thread.sleep(2000); } catch (InterruptedException ex) { @@ -435,4 +440,36 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { MessageNotifyUtil.Notify.error(Bundle.SolrSearchService_indexingError(), bba.getDisplayName()); } } + + /** + * Adds an artifact to the keyword search text index as a concantenation of + * all of its attributes. + * + * @param artifact The artifact to index. + * + * @throws org.sleuthkit.datamodel.TskCoreException + * @deprecated Call index(Content) instead. + */ + @Deprecated + @Override + public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException { + if (artifact == null) { + return; + } + + // We only support artifact indexing for Autopsy versions that use + // the negative range for artifact ids. + if (artifact.getArtifactID() > 0) { + return; + } + final Ingester ingester = Ingester.getDefault(); + + try { + ingester.indexMetaDataOnly(artifact); + ingester.indexText(new ArtifactTextExtractor(), artifact, null); + } catch (Ingester.IngesterException ex) { + throw new TskCoreException(ex.getCause().getMessage(), ex); + } + } + } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index b2d1ef37e1..7b3893df98 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -495,7 +495,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { * Create an account. */ try { - AccountFileInstance ccAccountInstance = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString(), MODULE_NAME, content); + AccountFileInstance ccAccountInstance = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString(), MODULE_NAME, content); ccAccountInstance.addAttributes(attributes); } catch (TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.SEVERE, "Error creating CCN account instance", ex); //NON-NLS diff --git a/NEWS.txt b/NEWS.txt index 54e0acb883..de1a1e576e 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,3 +1,23 @@ +---------------- VERSION 4.7.0 -------------- +New Features: +- A graph visualization was added to the Communications tool to make it easier to find messages and relationships. +- A new Application content viewer provides custom views of media files, SQLite files, and Plists. +- A data source processor that runs Volatility was added to support ingesting memory images. +- Reports (e.g., RegRipper output) generated by ingest modules are now indexed for keyword search. +- Passwords to open password protected archive files can be entered. +- PhotoRec carving module can be configured to keep corrupted files. +- Filters to reduce files processed by ingest modules can have data range conditions. +- L01 files can be imported as data sources. +- Block size can be supplied for local drives and for images for which SleuthKit auto detect fails. +- Assorted small enhancements are included. + +Bug Fixes: +- Memory leaks and other issues revealed by fuzzing the SleuthKit have +been fixed. +- Result views (upper right) and content views (lower right) stay in synch when switching result views. +- Concurrency bugs in the ingest tasks scheduler have been fixed. +- Assorted small bug fixes are included. + ---------------- VERSION 4.6.0 -------------- New Features: - A new Message content viewer was added to make it easier to view email message contents. diff --git a/RecentActivity/build.xml b/RecentActivity/build.xml index f64b4191ca..af346f58e7 100644 --- a/RecentActivity/build.xml +++ b/RecentActivity/build.xml @@ -23,7 +23,7 @@ - + diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml index 4b173c70c1..fd72fa7dfe 100644 --- a/RecentActivity/nbproject/project.xml +++ b/RecentActivity/nbproject/project.xml @@ -60,7 +60,7 @@ 10 - 10.10 + 10.11 diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java index 8ae8dc0261..8ec5b79d56 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java @@ -53,7 +53,7 @@ abstract class Extract { final void init() throws IngestModuleException { try { - currentCase = Case.getOpenCase(); + currentCase = Case.getCurrentCaseThrows(); tskCase = currentCase.getSleuthkitCase(); } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.Extract_indexError_message(), ex); @@ -126,7 +126,7 @@ abstract class Extract { "Extract.noOpenCase.errMsg=No open case available."}) void indexArtifact(BlackboardArtifact bbart) { try { - Blackboard blackboard = Case.getOpenCase().getServices().getBlackboard(); + Blackboard blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); // index the artifact for keyword search blackboard.indexArtifact(bbart); } catch (Blackboard.BlackboardException ex) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java index 3e8bb2d611..1af8761144 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java @@ -75,7 +75,7 @@ class ExtractIE extends Extract { ExtractIE() throws NoCurrentCaseException { moduleName = NbBundle.getMessage(ExtractIE.class, "ExtractIE.moduleName.text"); - moduleTempResultsDir = RAImageIngestModule.getRATempPath(Case.getOpenCase(), "IE") + File.separator + "results"; //NON-NLS + moduleTempResultsDir = RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), "IE") + File.separator + "results"; //NON-NLS JAVA_PATH = PlatformUtil.getJavaPath(); } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java index 9048c57da0..a47d33f22a 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java @@ -193,7 +193,7 @@ class Util { parent_path = parent_path.substring(0, index); List files = null; try { - FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); + FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); files = fileManager.findFiles(dataSource, name, parent_path); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.WARNING, "Error fetching 'index.data' files for Internet Explorer history."); //NON-NLS diff --git a/Running_Linux_OSX.txt b/Running_Linux_OSX.txt new file mode 100644 index 0000000000..386c4c5899 --- /dev/null +++ b/Running_Linux_OSX.txt @@ -0,0 +1,45 @@ +This document outlines how to run a packaged version of Autopsy on Linux or OS X. It does not cover how to compile it from source or the Windows installer. + + +* Prerequisites * + +The following need to be done at least once. They do not need to be repeated for each Autopsy release. + +- Install testdisk for photorec functionality +-- Linux: % sudo apt-get install testdisk +-- OS X: % brew install testdisk + +- Install Oracle Java and set JAVA_HOME. +-- Linux: Use the instructions here: https://medium.com/coderscorner/installing-oracle-java-8-in-ubuntu-16-10-845507b13343 +-- OS X: Use The Oracle website: https://www.java.com/ + Set JAVA_HOME with something like: export JAVA_HOME=`/usr/libexec/java_home` in .bash_profile + + +* Install The Sleuth Kit Java Bindings * + +Autopsy depends on a specific version of The Sleuth Kit. You need the Java libraries of The Sleuth Kit installed, which is not part of all packages. + +- Linux: Install the sleuthkit-java.deb file that you can download from github.com/sleuthkit/sleuthkit/releases. This will install libewf, etc. +-- % sudo apt install ./sleuthkit-java_4.6.0-1_amd64.deb + +- OS X: Install The Sleuth Kit from brew. +-- % brew install sleuthkit + + +* Install Autopsy * + +- Extract the contents of the Autopsy ZIP file to a folder. +- Open a terminal and cd into the Autopsy folder. +- Run the unix_setup.sh script to configure Autopsy + % sh unix_setup.sh + + +* Running Autopsy * + +- In a terminal, change to the ‘bin’ directory in the Autopsy folder. +- Run Autopsy + % ./autopsy + +* Limitations (Updated May 2018) * +- Timeline does not work on OS X +- Video thumbnails are not generated (need to get a consistent version of OpenCV) diff --git a/TSKVersion.xml b/TSKVersion.xml index 544d73ea7f..57096d8d35 100644 --- a/TSKVersion.xml +++ b/TSKVersion.xml @@ -1,3 +1,3 @@ - + diff --git a/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java b/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java index b1ff751b77..96f7d18fa3 100644 --- a/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java +++ b/Testing/src/org/sleuthkit/autopsy/testing/AutopsyTestCases.java @@ -32,6 +32,7 @@ import java.util.Date; import java.util.List; import java.util.Random; import java.util.logging.Level; +import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.JDialog; import javax.swing.text.JTextComponent; @@ -57,7 +58,6 @@ import org.netbeans.jemmy.operators.JTreeOperator; import org.netbeans.jemmy.operators.JTreeOperator.NoSuchPathException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.CaseDbConnectionInfo; diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 87a24f5c85..8f1ef48a4c 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -20,11 +20,11 @@ package org.sleuthkit.autopsy.testing; import java.io.File; import java.io.IOException; +import java.util.logging.Logger; import junit.framework.Test; import junit.framework.TestCase; import org.netbeans.jemmy.Timeouts; import org.netbeans.junit.NbModuleSuite; -import org.sleuthkit.autopsy.coreutils.Logger; /** * This test expects the following system properties to be set: img_path: The diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..8500f65855 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,51 @@ +version: 4.6.0.{build} + +cache: + - C:\Users\appveyor\.ant -> appveyor.yml + - C:\Users\appveyor\.ivy2 -> appveyor.yml + - C:\ProgramData\chocolatey\bin -> appveyor.yml + - C:\ProgramData\chocolatey\lib -> appveyor.yml + +branches: + only: + - develop + +image: Visual Studio 2015 +platform: x64 +init: + - ps: choco install ant --ignore-dependencies + - ps: $env:Path="C:\Program Files\Java\jdk1.8.0\bin;$($env:Path);C:\ProgramData\chocolatey\lib\ant" + - set PATH=C:\Python36-x64\';%PATH% +environment: + global: + TSK_HOME: "C:\\sleuthkit" + LIBVHDI_HOME: "C:\\libvhdi_64bit" + LIBVMDK_HOME: "C:\\libvmdk_64bit\\libvmdk" + LIBEWF_HOME: "C:\\libewf_64bit" + POSTGRESQL_HOME_64: "C:\\Program Files\\PostgreSQL\\9.5" + JDK_HOME: C:\Program Files\Java\jdk1.8.0 + PYTHON: "C:\\Python36-x64" + +install: + - ps: pushd C:\ + - git clone https://github.com/sleuthkit/sleuthkit + - ps: popd + +services: + - postgresql95 + +build_script: + - cd %TSK_HOME% + - python setupDevRepos.py + - python win32\updateAndBuildAll.py -m + - ps: pushd bindings/java + - ps: ant -version + - cmd: ant dist-PostgreSQL + - ps: popd + - cd %APPVEYOR_BUILD_FOLDER% + - cmd: ant -q build + - cd Core + - cmd: ant -q test + - cd .. + +test: off diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index b734326e07..ed3fd19542 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 28 Feb 2018 09:59:56 -0500 +#Tue, 08 May 2018 13:47:32 +0200 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.6.0 +currentVersion=Autopsy 4.7.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 3cf76dea10..f7256b8432 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Wed, 28 Feb 2018 09:59:56 -0500 -CTL_MainWindow_Title=Autopsy 4.6.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.6.0 +#Tue, 08 May 2018 13:47:32 +0200 +CTL_MainWindow_Title=Autopsy 4.7.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.7.0 diff --git a/build.xml b/build.xml index 9a8b8bd507..a1ff9d665a 100644 --- a/build.xml +++ b/build.xml @@ -91,19 +91,11 @@ - - - - - - - - - - - - - + + + + + @@ -153,9 +145,9 @@ - + - + @@ -294,9 +286,20 @@ - + + + + + + - + + + + + + + diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index f2f31448a0..84605ac34f 100755 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.6.0 +PROJECT_NUMBER = 4.7.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.6.0 +HTML_OUTPUT = 4.7.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen-user/adHocKeywordSearch.dox b/docs/doxygen-user/adHocKeywordSearch.dox index ec7c1ab689..f8e4881fb4 100644 --- a/docs/doxygen-user/adHocKeywordSearch.dox +++ b/docs/doxygen-user/adHocKeywordSearch.dox @@ -39,6 +39,11 @@ Substring match should be used where the search term is just part of a word, or Regex match can be used to search for a specific pattern. Regular expressions are supported using Lucene Regex Syntax which is documented here: https://www.elastic.co/guide/en/elasticsearch/reference/1.6/query-dsl-regexp-query.html#regexp-syntax. Wildcards are automatically added to the beginning and end of the regular expressions to ensure all matches are found. Additionally, the resulting hits are split on common token separator boundaries (e.g. space, newline, colon, exclamation point etc.) to make the resulting keyword hit more amenable to highlighting. +Note: Since Autopsy 4.4, boundary characters ('^' and '$') no longer work as word boundaries. Previously a search for "^[0-9]{5}$" would return all five +digit strings surrounded by some type of non-word characters. For example, "The number 12345 is.." would contain a match, while "123456789 people" would not. This was because the regex +was compared against each "word" in the document. In newer versions, the text is not split into words internally so this type of search no longer works. To get similar results, replace the +boundary characters with the specific characters that should represent a word break. For example, "^[0-9]{5}$" could become "[ \.\-\,][0-9]{5}[ \.\-\,]". + There is some validation on the regex but it's best to test on a sample image to make sure your regexes are correct and working as expected. One simple way to test is by creating a sample text file that your expression should match, ingesting it as a \ref ds_log "Logical File Set" and then running the regex query. > In the year 1885 in an article titled Current Notes, the quick brown fox first jumped over the lazy dog. diff --git a/docs/doxygen-user/case_management.dox b/docs/doxygen-user/case_management.dox index 56d6b81be5..b4d9d0daba 100644 --- a/docs/doxygen-user/case_management.dox +++ b/docs/doxygen-user/case_management.dox @@ -34,7 +34,7 @@ To open a case, either: "Open Recent Case" will always bring up a screen allowing you to select one of the recently opened cases. "Open Case" will do one of two things; - If multi-user cases are not enabled, it will bring up a file chooser that can be used to browse to the ".aut" file in the case directory of the desired case -- If multi-user case are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The following shows the multi-user case selection screen: +- If multi-user cases are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The multi-user case selection screen has a \ref ui_quick_search feature which can be used to quickly find a case in the table. The following shows the multi-user case selection screen: \image html multi_user_case_select.png diff --git a/docs/doxygen-user/communications.dox b/docs/doxygen-user/communications.dox index 008ce70b64..ced0a044d3 100644 --- a/docs/doxygen-user/communications.dox +++ b/docs/doxygen-user/communications.dox @@ -20,6 +20,8 @@ The middle column displays each account, its device and type, and the number of Selecting an account in the middle column will bring up the messages for that account in the right hand column. Here data about each message is displayed in the top section, and the messages itself can be seen in the bottom section (if applicable). +The middle column and the right hand column both have a \ref ui_quick_search feature which can be used to quickly find a visible item in their section's table. + \image html cvt_messages.png */ \ No newline at end of file diff --git a/docs/doxygen-user/images/case_properties.png b/docs/doxygen-user/images/case_properties.png new file mode 100644 index 0000000000..75784e1cc6 Binary files /dev/null and b/docs/doxygen-user/images/case_properties.png differ diff --git a/docs/doxygen-user/images/quick_search_configuration.png b/docs/doxygen-user/images/quick_search_configuration.png new file mode 100644 index 0000000000..986d667c0c Binary files /dev/null and b/docs/doxygen-user/images/quick_search_configuration.png differ diff --git a/docs/doxygen-user/images/quick_search_result.png b/docs/doxygen-user/images/quick_search_result.png new file mode 100644 index 0000000000..c11d8ffa74 Binary files /dev/null and b/docs/doxygen-user/images/quick_search_result.png differ diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index a72256c7f6..8aabf7a03b 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -39,6 +39,7 @@ The following topics are available here: - \subpage tree_viewer_page - \subpage result_viewer_page - \subpage content_viewer_page + - \subpage ui_quick_search - \subpage image_gallery_page - \subpage file_search_page - \subpage ad_hoc_keyword_search_page diff --git a/docs/doxygen-user/quick_start_guide.dox b/docs/doxygen-user/quick_start_guide.dox index 98e23da660..b12c65c2a3 100644 --- a/docs/doxygen-user/quick_start_guide.dox +++ b/docs/doxygen-user/quick_start_guide.dox @@ -59,6 +59,8 @@ If you are viewing files from the Views and Results nodes, you can right-click o If you want to search for single keywords, then you can use the search box in the upper right of the program. The results will be shown in a table in the upper right. +The tree on the left as well as the table on the right have a \ref ui_quick_search feature which can be used to quickly find a visible node. + You can tag (bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report. \subsection s2a Ingest Inbox diff --git a/docs/doxygen-user/timeline.dox b/docs/doxygen-user/timeline.dox index 349124bcdf..d55f9a43bf 100644 --- a/docs/doxygen-user/timeline.dox +++ b/docs/doxygen-user/timeline.dox @@ -69,6 +69,7 @@ The __Counts View__ shows a stacked bar chart. Use this type of graph to show ho The __Details View__ shows individual or groups of related events. Date/time is represented horizontally along the x-axis, but the vertical axis does not represent any specific units. You would use this interface to answer questions about what specific events happened in a given time frame or what events occurred before or after a given event. You would generally use this type of interface after using the Counts View to identify a period of time that you wanted details on. There can be a lot of details in this view and we have introduced zooming concepts, as described in the next section, to help with this. +The table on the bottom left hand side of the panel has a \ref ui_quick_search feature which can be used to quickly find a node in the table. Visualization settings ---------------------- diff --git a/docs/doxygen-user/ui_quick_search.dox b/docs/doxygen-user/ui_quick_search.dox new file mode 100644 index 0000000000..901427243e --- /dev/null +++ b/docs/doxygen-user/ui_quick_search.dox @@ -0,0 +1,24 @@ +/*! \page ui_quick_search UI Quick Search + +The user interface quick search feature allows you to search within the data on a panel for a given string, it will not search data in hidden columns or collapsed nodes. + +How to use it +----- +In order to use the search you need to select any item in the area you wish to search, and start typing. If user interface quick search is available in the area you have selected a search field will appear in the bottom left hand corner of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field. +\image html quick_search_result.PNG + +Configuration +----- +By default the search will match against the data in all fields which are in the currently selected area. The search will also ignore case by default. If you want to change either of these default behaviors you can click the magnifying glass with the down arrow icon and configure which columns will be searched as well as if the search should ignore case. +\image html quick_search_configuration.PNG + +Where it can be used +----- +- The \ref tree_viewer_page "tree viewer" +- The \ref result_viewer_page "table view" +- The \ref case_open "open multi-user case panel" +- The \ref timeline_page tool’s table view +- The \ref communications_page "Communication Visualization Tool's" browse panel +- The \ref communications_page "Communication Visualization Tool's" message panel + +*/ diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 3f4797ace0..5dbdbf1479 100755 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.6.0 +PROJECT_NUMBER = 4.7.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1063,7 +1063,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.6.0/ +HTML_OUTPUT = api-docs/4.7.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/nbproject/project.properties b/nbproject/project.properties index 781ec2c83b..3cbde84d3e 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,7 +4,7 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.6.0 +app.version=4.7.0 ### build.type must be one of: DEVELOPMENT, RELEASE #build.type=RELEASE build.type=DEVELOPMENT diff --git a/pythonExamples/README.txt b/pythonExamples/README.txt index 3564182ec9..cbf1d60095 100644 --- a/pythonExamples/README.txt +++ b/pythonExamples/README.txt @@ -5,7 +5,7 @@ your needs. See the developer guide for more details and how to use and load the modules. - http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/index.html + http://sleuthkit.org/autopsy/docs/api-docs/4.7.0/index.html Each module in this folder should have a brief description about what they can do. diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index 2e738ef588..110c3b8ede 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -36,7 +36,7 @@ 10 - 10.10 + 10.11 @@ -45,7 +45,7 @@ 6 - 6.3 + 6.5 diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 1e439cb39d..bfa1542ce1 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -79,7 +79,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; try { - fileManager = Case.getOpenCase().getServices().getFileManager(); + fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); throw new IngestModuleException(Bundle.ThunderbirdMboxFileIngestModule_noOpenCase_errMsg(), ex); @@ -90,7 +90,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { public ProcessResult process(AbstractFile abstractFile) { try { - blackboard = Case.getOpenCase().getServices().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return ProcessResult.ERROR; @@ -306,8 +306,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @throws NoCurrentCaseException if there is no open case. * @return the temporary folder */ - public static String getTempPath() throws NoCurrentCaseException { - String tmpDir = Case.getOpenCase().getTempDirectory() + File.separator + static String getTempPath() throws NoCurrentCaseException { + String tmpDir = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + "EmailParser"; //NON-NLS File dir = new File(tmpDir); if (dir.exists() == false) { @@ -322,8 +322,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @throws NoCurrentCaseException if there is no open case. * @return the module output folder */ - public static String getModuleOutputPath() throws NoCurrentCaseException { - String outDir = Case.getOpenCase().getModuleDirectory() + File.separator + static String getModuleOutputPath() throws NoCurrentCaseException { + String outDir = Case.getCurrentCaseThrows().getModuleDirectory() + File.separator + EmailParserModuleFactory.getModuleName(); File dir = new File(outDir); if (dir.exists() == false) { @@ -338,8 +338,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @throws NoCurrentCaseException if there is no open case. * @return the relative path of the module output folder */ - public static String getRelModuleOutputPath() throws NoCurrentCaseException { - return Case.getOpenCase().getModuleOutputDirectoryRelativePath() + File.separator + static String getRelModuleOutputPath() throws NoCurrentCaseException { + return Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath() + File.separator + EmailParserModuleFactory.getModuleName(); } @@ -460,7 +460,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { AccountFileInstance senderAccountInstance = null; - Case openCase = Case.getOpenCase(); + Case openCase = Case.getCurrentCaseThrows(); if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); diff --git a/unix_setup.sh b/unix_setup.sh index 93a98ae1bc..970c35a5c2 100755 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -2,12 +2,13 @@ # Verifies programs are installed and copies native code into the Autopsy folder structure -TSK_VERSION=4.6.0 +TSK_VERSION=4.6.1 # Verify PhotoRec was installed photorec_filepath=/usr/bin/photorec -if [ -f "$photorec_filepath" ]; then - echo "$photorec_filepath found" +photorec_osx_filepath=/usr/local/bin/photorec +if [ -f "$photorec_filepath" ] || [ -f "$photorec_osx_filepath" ]; then + echo "photorec found" else echo "ERROR: Photorec not found, please install the testdisk package" exit 1 @@ -27,12 +28,23 @@ else fi # Verify Sleuth Kit Java was installed -sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar; + + +if [ -f "/usr/share/java/sleuthkit-$TSK_VERSION.jar" ]; then + sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar +elif [ -f "/usr/local/share/java/sleuthkit-$TSK_VERSION.jar" ]; then + sleuthkit_jar_filepath=/usr/local/share/java/sleuthkit-$TSK_VERSION.jar +else + echo "sleuthkit.jar file not found" + echo "exiting .." + exit 1 +fi + ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-postgresql-$TSK_VERSION.jar; if [ -f "$sleuthkit_jar_filepath" ]; then echo "$sleuthkit_jar_filepath found" echo "Copying into the Autopsy directory" - rm $ext_jar_filepath; + rm -f $ext_jar_filepath; if [ "$?" -gt 0 ]; then #checking if remove operation failed echo "exiting .." exit 1