diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java index e1b45612af..4d00a60606 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/UpdateRecentCases.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2015 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,7 @@ class UpdateRecentCases extends JMenuItem implements DynamicMenuContent { menuItem.setActionCommand(caseName[i].toUpperCase()); menuItem.addActionListener(new RecentItems(caseName[i], casePath[i])); comps[i] = menuItem; - hasRecentCase = hasRecentCase || true; + hasRecentCase = true; } } diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 8bd8328eca..15a783fecb 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -202,6 +202,13 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties index 9cf8cdc91c..3fa835fdfd 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties @@ -1,4 +1,5 @@ OpenIDE-Module-Name=Ingest +Menu/Tools/RunIngestModules=Run Ingest Modules CTL_IngestMessageTopComponent=Messages HINT_IngestMessageTopComponent=Messages windows IngestMessageDetailsPanel.backButton.text= @@ -105,3 +106,4 @@ IngestJobSettingsPanel.processUnallocCheckbox.toolTipText=Processes unallocated IngestJobSettingsPanel.processUnallocCheckbox.text=Process Unallocated Space IngestManager.cancellingIngest.msgDlg.text=Cancelling all currently running ingest jobs IngestManager.serviceIsDown.msgDlg.text={0} is down +RunIngestSubMenu.menuItem.empty=-Empty- diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java new file mode 100755 index 0000000000..0bcd23b8b9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestAction.java @@ -0,0 +1,93 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2015 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.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JMenuItem; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.actions.CallableSystemAction; +import org.openide.util.actions.Presenter; + +@ActionID( + category = "Tools", + id = "org.sleuthkit.autopsy.ingest.RunIngestAction" +) +@ActionRegistration( + displayName = "#CTL_RunIngestAction" +) +@Messages("CTL_RunIngestAction=Run Ingest") +public final class RunIngestAction extends CallableSystemAction implements Presenter.Menu, ActionListener { + + static public RunIngestAction getInstance() { + return new RunIngestAction(); + } + + /** + * Call getMenuPresenters to create images sublist + */ + @Override + public void performAction() { + getMenuPresenter(); + } + + /** + * Gets the name of this action. This may be presented as an item in a menu. + * + * @return actionName + */ + @Override + public String getName() { + return NbBundle.getMessage(RunIngestAction.class, "RunIngestModulesMenu.getName.text"); + } + + /** + * Gets the HelpCtx associated with implementing object + * + * @return HelpCtx or HelpCtx.DEFAULT_HELP + */ + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + /** + * Create a sublist of images updated by RunIngestSubMenu + * Each has an action to perform Ingest Modules on it. + * + * @return the images sublist created. + */ + @Override + public JMenuItem getMenuPresenter() { + JMenuItem sublist = new RunIngestSubMenu(); + sublist.setVisible(true); + return sublist; + } + + /** + * This method does nothing, use performAction instead. + */ + @Override + public void actionPerformed(ActionEvent e) { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesAction.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesAction.java new file mode 100755 index 0000000000..50a2319529 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesAction.java @@ -0,0 +1,52 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2015 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.awt.event.ActionEvent; +import java.util.Collections; +import javax.swing.AbstractAction; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; + +/** + * This class is used to add the action to the run ingest modules menu item. + * When the image is pressed, it should open the wizard for ingest modules. + */ +final class RunIngestModulesAction extends AbstractAction { + + Image image; + + /** + * the constructor + */ + public RunIngestModulesAction(Image image) { + this.image = image; + } + + /** + * Runs the ingest modules wizard on the image. + * + * @param e the action event + */ + @Override + public void actionPerformed(ActionEvent e) { + final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.singletonList(image)); + ingestDialog.display(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesDialog.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesDialog.java index 4b81506603..da491fd22a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestModulesDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2014 Basis Technology Corp. + * Copyright 2013-2015 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java new file mode 100755 index 0000000000..f4db5d47e8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/RunIngestSubMenu.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2015 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.util.ArrayList; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This class is used to populate the list of open images to run ingest on them + */ +final class RunIngestSubMenu extends JMenuItem implements DynamicMenuContent { + + /** + * Creates main menu/popup menu items. It's called each time a popup menu + * is constructed and just once for the main menu. + * Main menu updates happen through the synchMenuPresenters() method. + * + * @return + */ + @Override + public JComponent[] getMenuPresenters() { + List images = new ArrayList<>(); + + try { + SleuthkitCase sk = Case.getCurrentCase().getSleuthkitCase(); + images = sk.getImages(); + } catch (IllegalStateException ex) { + // No open Cases, create a disabled empty menu + return getEmpty(); + } catch (TskCoreException e) { + System.out.println("Exception getting images: " + e.getMessage()); + } + JComponent[] comps = new JComponent[images.size()]; + + // Add Images to the component list + for (int i = 0; i < images.size(); i++) { + String action = images.get(i).getName(); + JMenuItem menuItem = new JMenuItem(action); + menuItem.setActionCommand(action.toUpperCase()); + menuItem.addActionListener(new RunIngestModulesAction(images.get(i))); + comps[i] = menuItem; + } + // If no images are open, create a disabled empty menu + if (images.isEmpty()) { + return getEmpty(); + } + return comps; + } + + // returns a disabled empty menu + private JComponent[] getEmpty() { + JComponent[] comps = new JComponent[1]; + JMenuItem emptyMenu = new JMenuItem(NbBundle.getMessage(RunIngestSubMenu.class, "RunIngestSubMenu.menuItem.empty")); + comps[0] = emptyMenu; + comps[0].setEnabled(false); + return comps; + } + + /** + * Updates main menu presenters. This method is called only by the main menu + * processing. + * + * @param jcs the previously used menu items returned by previous call to + * getMenuPresenters() or synchMenuPresenters() + * + * @return menu a new set of items to show in menu. Can be either an updated + * old set of instances or a completely new one. + */ + @Override + public JComponent[] synchMenuPresenters(JComponent[] jcs) { + return getMenuPresenters(); + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/e01verify/E01VerifyIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/e01verify/E01VerifyIngestModule.java index d0a813b61b..c4b1df80b2 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/e01verify/E01VerifyIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/e01verify/E01VerifyIngestModule.java @@ -23,6 +23,7 @@ import java.security.NoSuchAlgorithmException; import java.util.logging.Level; import javax.xml.bind.DatatypeConverter; import org.openide.util.NbBundle; +import org.python.bouncycastle.util.Arrays; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; @@ -48,7 +49,6 @@ public class E01VerifyIngestModule implements DataSourceIngestModule { private MessageDigest messageDigest; private boolean verified = false; - private boolean skipped = false; private String calculatedHash = ""; private String storedHash = ""; private IngestJobContext context; @@ -75,8 +75,10 @@ public class E01VerifyIngestModule implements DataSourceIngestModule { @Override public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) { String imgName = dataSource.getName(); + + // Skip non-images if (!(dataSource instanceof Image)) { - logger.log(Level.INFO, "Skipping disk image image {0}", imgName); //NON-NLS + logger.log(Level.INFO, "Skipping non-image {0}", imgName); //NON-NLS services.postMessage(IngestMessage.createMessage(MessageType.INFO, E01VerifierModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.process.skipNonEwf", @@ -95,16 +97,17 @@ public class E01VerifyIngestModule implements DataSourceIngestModule { return ProcessResult.OK; } - if ((img.getMd5() != null) && !img.getMd5().isEmpty()) { - storedHash = img.getMd5().toLowerCase(); - logger.log(Level.INFO, "Hash value stored in {0}: {1}", new Object[]{imgName, storedHash}); //NON-NLS - } else { + // Report an error for null or empty MD5 + if ((img.getMd5() == null) || img.getMd5().isEmpty()) { services.postMessage(IngestMessage.createMessage(MessageType.ERROR, E01VerifierModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.process.noStoredHash", imgName))); return ProcessResult.ERROR; } + + storedHash = img.getMd5().toLowerCase(); + logger.log(Level.INFO, "Hash value stored in {0}: {1}", new Object[]{imgName, storedHash}); //NON-NLS logger.log(Level.INFO, "Starting hash verification of {0}", img.getName()); //NON-NLS services.postMessage(IngestMessage.createMessage(MessageType.INFO, E01VerifierModuleFactory.getModuleName(), @@ -126,11 +129,12 @@ public class E01VerifyIngestModule implements DataSourceIngestModule { long chunkSize = 64 * img.getSsize(); chunkSize = (chunkSize == 0) ? DEFAULT_CHUNK_SIZE : chunkSize; - int totalChunks = (int) Math.ceil(size / chunkSize); + // Casting to double to capture decimals + int totalChunks = (int) Math.ceil((double)size / (double)chunkSize); logger.log(Level.INFO, "Total chunks = {0}", totalChunks); //NON-NLS int read; - byte[] data; + byte[] data = new byte[(int) chunkSize]; statusHelper.switchToDeterminate(totalChunks); // Read in byte size chunks and update the hash value with the data. @@ -138,7 +142,6 @@ public class E01VerifyIngestModule implements DataSourceIngestModule { if (context.dataSourceIngestIsCancelled()) { return ProcessResult.OK; } - data = new byte[(int) chunkSize]; try { read = img.read(data, i * chunkSize, chunkSize); } catch (TskCoreException ex) { @@ -148,7 +151,14 @@ public class E01VerifyIngestModule implements DataSourceIngestModule { logger.log(Level.SEVERE, msg, ex); return ProcessResult.ERROR; } - messageDigest.update(data); + + // Only update with the read bytes. + if(read == chunkSize) { + messageDigest.update(data); + } else { + byte[] subData = Arrays.copyOfRange(data, 0, read); + messageDigest.update(subData); + } statusHelper.progress(i); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index a6b440de33..2416001856 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -87,6 +87,9 @@ public class HashDbIngestModule implements FileIngestModule { updateEnabledHashSets(hashDbManager.getKnownFileHashSets(), knownHashSets); if (refCounter.incrementAndGet(jobId) == 1) { + // initialize job totals + getTotalsForIngestJobs(jobId); + // if first module for this job then post error msgs if needed if (knownBadHashSets.isEmpty()) { diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties index c7f862b1fc..5e74024afe 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties @@ -3,6 +3,7 @@ CTL_ReportWizardAction=Run Report ArtifactSelectionDialog.titleLabel.text=Select which artifacts you would like to report on: ArtifactSelectionDialog.okButton.text=OK ReportVisualPanel1.reportModulesLabel.text=Report Modules: +ReportVisualPanel1.invalidModuleWarning=Encountered an invalid Report Module ({0}) DefaultReportConfigurationPanel.infoLabel.text=This report will be configured on the next screen. ReportVisualPanel2.dataLabel.text=Select which data to report on: ReportVisualPanel2.deselectAllButton.text=Deselect All diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index e302207668..26f5f2ee3c 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -151,7 +151,7 @@ class ReportGenerator { if (entry.getValue()) { TableReportModule module = entry.getKey(); String reportFilePath = module.getRelativeFilePath(); - if (reportFilePath != null) { + if (!reportFilePath.isEmpty()) { tableProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath)); } else { tableProgress.put(module, panel.addReport(module.getName(), null)); @@ -165,7 +165,7 @@ class ReportGenerator { if (entry.getValue()) { GeneralReportModule module = entry.getKey(); String reportFilePath = module.getRelativeFilePath(); - if (reportFilePath != null) { + if (!reportFilePath.isEmpty()) { generalProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath)); } else { generalProgress.put(module, panel.addReport(module.getName(), null)); @@ -179,7 +179,7 @@ class ReportGenerator { if (entry.getValue()) { FileReportModule module = entry.getKey(); String reportFilePath = module.getRelativeFilePath(); - if (reportFilePath != null) { + if (!reportFilePath.isEmpty()) { fileProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath)); } else { fileProgress.put(module, panel.addReport(module.getName(), null)); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java index acd49f029e..9b6acfd092 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java @@ -27,6 +27,7 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JRadioButton; @@ -34,11 +35,12 @@ import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.python.JythonModuleLoader; -import org.sleuthkit.autopsy.report.ReportHTML; final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { @@ -64,23 +66,39 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { // Initialize the list of ReportModules private void initModules() { for (TableReportModule module : Lookup.getDefault().lookupAll(TableReportModule.class)) { - tableModules.add(module); - modules.add(module); + if (moduleIsValid(module)) { + tableModules.add(module); + modules.add(module); + } else { + popupWarning(module); + } } for (GeneralReportModule module : Lookup.getDefault().lookupAll(GeneralReportModule.class)) { - generalModules.add(module); - modules.add(module); + if (moduleIsValid(module)) { + generalModules.add(module); + modules.add(module); + } else { + popupWarning(module); + } } for (GeneralReportModule module : JythonModuleLoader.getGeneralReportModules()) { - generalModules.add(module); - modules.add(module); + if (moduleIsValid(module)) { + generalModules.add(module); + modules.add(module); + } else { + popupWarning(module); + } } for (FileReportModule module : Lookup.getDefault().lookupAll(FileReportModule.class)) { - fileModules.add(module); - modules.add(module); + if (moduleIsValid(module)) { + fileModules.add(module); + modules.add(module); + } else { + popupWarning(module); + } } Collections.sort(modules, new Comparator() { @@ -117,6 +135,20 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { modulesJList.setSelectedIndex(selectedIndex); } + // Make sure that the report module has a valid non-null name. + private boolean moduleIsValid(ReportModule module) { + return module.getName() != null && !module.getName().isEmpty() + && module.getRelativeFilePath() != null; + } + + private void popupWarning(ReportModule module) { + String moduleClassName = module.getClass().getSimpleName(); + logger.log(Level.WARNING, "Invalid ReportModule: {0}", moduleClassName); // NON_NLS + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( + NbBundle.getMessage(ReportVisualPanel1.class, "ReportVisualPanel1.invalidModuleWarning", moduleClassName), + NotifyDescriptor.ERROR_MESSAGE)); + } + @Override public String getName() { return NbBundle.getMessage(this.getClass(), "ReportVisualPanel1.getName.text"); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/FXMLConstructor.java b/Core/src/org/sleuthkit/autopsy/timeline/FXMLConstructor.java index cdf6a38455..0c90af7f27 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/FXMLConstructor.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/FXMLConstructor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,12 @@ package org.sleuthkit.autopsy.timeline; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.logging.Level; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * This class supports programmer productivity by abstracting frequently used @@ -34,32 +36,53 @@ import org.openide.util.Exceptions; * at * http://stackoverflow.com/questions/11734885/javafx2-very-poor-performance-when-adding-custom-made-fxmlpanels-to-gridpane. * + * NOTE: As described in the link above above, using FXMLConstructor will be + * inefficient if FXML is used as a template for many similar items. In that use + * case, it is much faster to build the entire hierarchy in Java. This class is + * intended only to remove the boilerplate initialization code when defining a + * relatively static layout + * * TODO: find a way to move this to CoreUtils and remove duplicate verison in * image analyzer */ public class FXMLConstructor { - static public void construct(Node n, String fxmlFileName) { - final String name = "nbres:/" + StringUtils.replace(n.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS - System.out.println(name); + private static final Logger LOGGER = Logger.getLogger(FXMLConstructor.class.getName()); + + /** + * Load an fxml file and initialize a node with it. Since this manipulates + * the node, it must be called on the JFX thread. + * + * + * @param node a node to initialize from a loaded FXML + * @param fxmlFileName the the file name of the FXML to load, relative to + * the package that the class of node is defined in. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + static public void construct(Node node, String fxmlFileName) { + final String name = "nbres:/" + StringUtils.replace(node.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS try { FXMLLoader fxmlLoader = new FXMLLoader(new URL(name)); - fxmlLoader.setRoot(n); - fxmlLoader.setController(n); + fxmlLoader.setRoot(node); + fxmlLoader.setController(node); try { fxmlLoader.load(); } catch (IOException exception) { + LOGGER.log(Level.SEVERE, "FXMLConstructor was unable to load FXML, falling back on default Class Loader, and trying again.", exception); try { fxmlLoader.setClassLoader(FXMLLoader.getDefaultClassLoader()); fxmlLoader.load(); } catch (IOException ex) { - Exceptions.printStackTrace(ex); + LOGGER.log(Level.SEVERE, "FXMLConstructor was unable to load FXML, node initialization may not be complete.", ex); } } } catch (MalformedURLException ex) { - Exceptions.printStackTrace(ex); + LOGGER.log(Level.SEVERE, "FXMLConstructor was unable to load FXML, node initialization may not be complete.", ex); } } + + private FXMLConstructor() { + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/MiscTypes.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/MiscTypes.java index ef57868a1d..6f59c8f981 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/MiscTypes.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/MiscTypes.java @@ -23,10 +23,11 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.BiFunction; +import java.util.logging.Level; import javafx.scene.image.Image; import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -57,7 +58,7 @@ public enum MiscTypes implements EventType, ArtifactEventType { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION), - (BlackboardArtifact artf, Map attrMap) -> { + (artf, attrMap) -> { final BlackboardAttribute latStart = attrMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); final BlackboardAttribute longStart = attrMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); final BlackboardAttribute latEnd = attrMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); @@ -129,18 +130,16 @@ public enum MiscTypes implements EventType, ArtifactEventType { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL), - (BlackboardArtifact t, - Map u) -> { + (artifact, attributeMap) -> { try { - AbstractFile f = t.getSleuthkitCase().getAbstractFileById(t.getObjectID()); - if (f != null) { - return f.getName(); + AbstractFile file = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + if (file != null) { + return file.getName(); } - return " error loading file name"; // NON-NLS } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); - return " error loading file name"; // NON-NLS + Logger.getLogger(MiscTypes.class.getName()).log(Level.SEVERE, "Exif event type failed to look up backing file name", ex); } + return " error loading file name"; // NON-NLS }), DEVICES_ATTACHED(NbBundle.getMessage(MiscTypes.class, "MiscTypes.devicesAttached.name"), "usb_devices.png", // NON-NLS BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED, @@ -185,26 +184,41 @@ public enum MiscTypes implements EventType, ArtifactEventType { private final BiFunction, String> shortExtractor; + /** + * {@inheritDoc } + */ @Override public BiFunction, String> getFullExtractor() { return longExtractor; } + /** + * {@inheritDoc } + */ @Override public BiFunction, String> getMedExtractor() { return medExtractor; } + /** + * {@inheritDoc } + */ @Override public BiFunction, String> getShortExtractor() { return shortExtractor; } + /** + * {@inheritDoc } + */ @Override public BlackboardAttribute.ATTRIBUTE_TYPE getDateTimeAttrubuteType() { return dateTimeAttributeType; } + /** + * {@inheritDoc } + */ @Override public EventTypeZoomLevel getZoomLevel() { return EventTypeZoomLevel.SUB_TYPE; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 2c6ec00145..c23d68c5da 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -1071,9 +1071,12 @@ public class EventDB { String timeZone = TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : ""; // NON-NLS String typeColumn = typeColumnHelper(useSubTypes); - //compose query string + //compose query string, new-lines only for nicer formatting if printing the entire query String query = "SELECT strftime('" + strfTimeFormat + "',time , 'unixepoch'" + timeZone + ") AS interval," // NON-NLS - + " group_concat(events.event_id) as event_ids, min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS + + "\n group_concat(events.event_id) as event_ids," + + "\n group_concat(CASE WHEN hash_hit = 1 THEN event_id ELSE NULL END) as hash_hits," + + "\n group_concat(CASE WHEN tagged = 1 THEN event_id ELSE NULL END) as taggeds," + + "\n min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS + "\n FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) // NON-NLS + "\n WHERE time >= " + start + " AND time < " + end + " AND " + SQLHelper.getSQLWhere(filter) // NON-NLS + "\n GROUP BY interval, " + typeColumn + " , " + descriptionColumn // NON-NLS @@ -1081,6 +1084,7 @@ public class EventDB { // perform query and map results to AggregateEvent objects List events = new ArrayList<>(); + DBLock.lock(); try (Statement createStatement = con.createStatement(); @@ -1117,23 +1121,8 @@ public class EventDB { String description = rs.getString(SQLHelper.getDescriptionColumn(descriptionLOD)); EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS - Set hashHits = new HashSet<>(); - String hashHitQuery = "SELECT group_concat(event_id) FROM events WHERE event_id IN (" + eventIDsString + ") AND hash_hit = 1";// NON-NLS - try (Statement stmt = con.createStatement(); - ResultSet hashHitsRS = stmt.executeQuery(hashHitQuery)) { - while (hashHitsRS.next()) { - hashHits = SQLHelper.unGroupConcat(hashHitsRS.getString("group_concat(event_id)"), Long::valueOf);// NON-NLS - } - } - - Set tagged = new HashSet<>(); - String taggedQuery = "SELECT group_concat(event_id) FROM events WHERE event_id IN (" + eventIDsString + ") AND tagged = 1";// NON-NLS - try (Statement stmt = con.createStatement(); - ResultSet taggedRS = stmt.executeQuery(taggedQuery)) { - while (taggedRS.next()) { - tagged = SQLHelper.unGroupConcat(taggedRS.getString("group_concat(event_id)"), Long::valueOf);// NON-NLS - } - } + Set hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); + Set tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); return new AggregateEvent(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index 83e8801532..82e0531b46 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Level; import javafx.beans.Observable; import javax.swing.Action; import org.joda.time.DateTime; @@ -29,8 +30,8 @@ import org.joda.time.DateTimeZone; import org.openide.nodes.Children; import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; -import org.openide.util.Exceptions; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -46,6 +47,8 @@ import org.sleuthkit.datamodel.Content; */ class EventNode extends DisplayableItemNode { + private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName()); + private final TimeLineEvent e; EventNode(TimeLineEvent eventById, AbstractFile file, BlackboardArtifact artifact) { @@ -75,7 +78,7 @@ class EventNode extends DisplayableItemNode { try { timePropery.setValue(getDateTimeString()); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - Exceptions.printStackTrace(ex); + LOGGER.log(Level.SEVERE, "unexpected error setting date/time property on EventNode explorer node", ex); } }); @@ -105,7 +108,7 @@ class EventNode extends DisplayableItemNode { final List factoryActions = DataModelActionsFactory.getActions(content, artifact != null); actionsList.addAll(factoryActions); - return actionsList.toArray(new Action[0]); + return actionsList.toArray(new Action[actionsList.size()]); } @Override @@ -118,7 +121,11 @@ class EventNode extends DisplayableItemNode { throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates. } - class TimeProperty extends PropertySupport.ReadWrite { + /** + * We use TimeProperty instead of a normal NodeProperty to correctly display + * the date/time when the user changes the timezone setting. + */ + private class TimeProperty extends PropertySupport.ReadWrite { private String value; @@ -127,7 +134,7 @@ class EventNode extends DisplayableItemNode { return false; } - public TimeProperty(String name, String displayName, String shortDescription, String value) { + TimeProperty(String name, String displayName, String shortDescription, String value) { super(name, String.class, displayName, shortDescription); setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS this.value = value;