diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java index c723d99b55..1673fd577f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java @@ -51,7 +51,7 @@ public abstract class DisplayableItemNode extends AbstractNode { * * @throws TskCoreException */ - static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException { + protected static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException { BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID)); if (pathIDAttribute != null) { long contentID = pathIDAttribute.getValueLong(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index aa52b8d7be..d79cbce173 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -59,7 +59,6 @@ import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.actions.AddBookmarkTagAction; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponents.DataContentPanel; import org.sleuthkit.autopsy.corecomponents.DataResultPanel; @@ -168,7 +167,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer // make a copy because this list gets updated as the user navigates around // and causes concurrent access exceptions List selectedEventIDs = ImmutableList.copyOf(controller.getSelectedEventIDs()); - + //depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly. switch (controller.getViewMode()) { case LIST: @@ -198,9 +197,6 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer contentViewerPanel.setNode(null); } }); - } catch (NoCurrentCaseException ex) { - //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. - logger.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS Platform.runLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index 4ee15bc76a..dc14d3d81d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,12 @@ package org.sleuthkit.autopsy.timeline.explorernodes; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.logging.Level; +import javax.annotation.Nonnull; import javax.swing.Action; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -31,9 +34,11 @@ import org.openide.nodes.Children; import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; @@ -46,7 +51,6 @@ import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.autopsy.timeline.ui.EventTypeUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -59,17 +63,32 @@ public class EventNode extends DisplayableItemNode { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName()); + private static final Logger logger = Logger.getLogger(EventNode.class.getName()); private final TimelineEvent event; - EventNode(TimelineEvent event, Content file, BlackboardArtifact artifact) { + /** + * Construct an EvetNode for an event with a Content and a + * BlackboardArtifact in its lookup. + * + * @param event The event this node is for. + * @param file The Content the artifact for this event is derived form. + * Not Null. + * @param artifact The artifact this event is derived from. Not Null. + */ + EventNode(@Nonnull TimelineEvent event, @Nonnull Content file, @Nonnull BlackboardArtifact artifact) { super(Children.LEAF, Lookups.fixed(event, file, artifact)); this.event = event; this.setIconBaseWithExtension(EventTypeUtils.getImagePath(event.getEventType())); // NON-NLS } - EventNode(TimelineEvent event, Content file) { + /** + * Construct an EvetNode for an event with a Content in its lookup. + * + * @param event The event this node is for. + * @param file The Content this event is derived directly from. Not Null. + */ + EventNode(@Nonnull TimelineEvent event, @Nonnull Content file) { super(Children.LEAF, Lookups.fixed(event, file)); this.event = event; this.setIconBaseWithExtension(EventTypeUtils.getImagePath(event.getEventType())); // NON-NLS @@ -117,40 +136,43 @@ public class EventNode extends DisplayableItemNode { "EventNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. " + " The 'View File in Timeline' action will not be available."}) public Action[] getActions(boolean context) { - Action[] superActions = super.getActions(context); List actionsList = new ArrayList<>(); - actionsList.addAll(Arrays.asList(superActions)); - - final Content sourceFile = getLookup().lookup(Content.class); + Collections.addAll(actionsList, super.getActions(context)); /* - * if this event is derived from an artifact, add actions to view the + * If this event is derived from an artifact, add actions to view the * source file and a "linked" file, if present. */ final BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class); + final Content sourceFile = getLookup().lookup(Content.class); if (artifact != null) { try { + //find a linked file such as a downloaded file. AbstractFile linkedfile = findLinked(artifact); if (linkedfile != null) { actionsList.add(ViewFileInTimelineAction.createViewFileAction(linkedfile)); } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS + logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.EventNode_getAction_errorTitle(), Bundle.EventNode_getAction_linkedFileMessage()); } - //if this event has associated content, add the action to view the content in the timeline - if (null != sourceFile) { - if (sourceFile instanceof AbstractFile) { - actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction((AbstractFile) sourceFile)); - } + //add the action to view the content in the timeline, only for abstract files ( ie with times) + if (sourceFile instanceof AbstractFile) { + actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction((AbstractFile) sourceFile)); } } //get default actions for the source file - final List factoryActions = DataModelActionsFactory.getActions(sourceFile, artifact != null); - + List factoryActions = DataModelActionsFactory.getActions(sourceFile, artifact != null); actionsList.addAll(factoryActions); + if (factoryActions.isEmpty()) { // if there were no factory supplied actions, at least add the tagging actions. + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + if (isExactlyOneArtifactSelected()) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + } return actionsList.toArray(new Action[actionsList.size()]); } @@ -190,7 +212,7 @@ public class EventNode extends DisplayableItemNode { try { setValue(getDateTimeString()); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - LOGGER.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS + logger.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS } }); @@ -202,10 +224,10 @@ public class EventNode extends DisplayableItemNode { } @Override - public void setValue(String t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public void setValue(String newValue) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { String oldValue = getValue(); - value = t; - firePropertyChange("time", oldValue, t); // NON-NLS + value = newValue; + firePropertyChange("time", oldValue, newValue); // NON-NLS } } @@ -216,55 +238,31 @@ public class EventNode extends DisplayableItemNode { * @param eventID The ID of the event this node is for. * @param eventsModel The model that provides access to the events DB. * - * @return An EventNode with the file (and artifact) backing this event in - * its lookup. + * @return An EventNode with the content (and possible artifact) backing + * this event in its lookup. */ - public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException, NoCurrentCaseException { + public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException { - SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - try { - /* - * Look up the event by id and creata an EventNode with the - * appropriate data in the lookup. - */ - final TimelineEvent eventById = eventsModel.getEventById(eventID); + SleuthkitCase sleuthkitCase = eventsModel.getSleuthkitCase(); - Content file = sleuthkitCase.getContentById(eventById.getFileObjID()); + /* + * Look up the event by id and creata an EventNode with the appropriate + * data in the lookup. + */ + final TimelineEvent eventById = eventsModel.getEventById(eventID); + Content file = sleuthkitCase.getContentById(eventById.getFileObjID()); - if (eventById.getArtifactID().isPresent()) { - BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); - return new EventNode(eventById, file, blackboardArtifact); - } else { - return new EventNode(eventById, file); - } - } catch (TskCoreException ex) { - throw new TskCoreException("Error getting event by id.", ex); + if (eventById.getArtifactID().isPresent()) { + BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); + return new EventNode(eventById, file, blackboardArtifact); + } else { + return new EventNode(eventById, file); } } - /** - * this code started as a cut and past of - * DataResultFilterNode.GetPopupActionsDisplayableItemNodeVisitor.findLinked(BlackboardArtifactNode - * ba) - * - * It is now in DisplayableItemNode too, but is not accesible across - * packages - * - * @param artifact - * - * @return - */ - static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException { - - BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID)); - - if (pathIDAttribute != null) { - long contentID = pathIDAttribute.getValueLong(); - if (contentID != -1) { - return artifact.getSleuthkitCase().getAbstractFileById(contentID); - } - } - - return null; + private static boolean isExactlyOneArtifactSelected() { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + return selectedArtifactsList.size() == 1; } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index aa6865471f..e7c14d87f9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -29,7 +29,6 @@ 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.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -85,8 +84,7 @@ public class EventRootNode extends DisplayableItemNode { * filteredEvents is used to lookup the events from their IDs */ private final FilteredEventsModel filteredEvents; - private Map nodesMap = new HashMap<>(); - + private final Map nodesMap = new HashMap<>(); EventNodeChildFactory(Collection eventIds, FilteredEventsModel filteredEvents) { this.eventIDs = eventIds; @@ -100,16 +98,12 @@ public class EventRootNode extends DisplayableItemNode { * indicate this. */ if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) { - for (Long eventId: eventIDs){ - if (!nodesMap.containsKey(eventId)) { - nodesMap.put(eventId, createNode(eventId)); - } + for (Long eventId : eventIDs) { + nodesMap.computeIfAbsent(eventId, this::createNode); toPopulate.add(eventId); } } else { - if (!nodesMap.containsKey(-1L)) { - nodesMap.put(-1L, createNode(-1L)); - } + nodesMap.computeIfAbsent(-1L, this::createNode); toPopulate.add(-1L); } return true; @@ -119,9 +113,8 @@ public class EventRootNode extends DisplayableItemNode { protected Node createNodeForKey(Long eventID) { return nodesMap.get(eventID); } - + private Node createNode(Long eventID) { - if (eventID < 0) { /* * If the eventId is a the special value ( -1 ), return a node @@ -131,17 +124,13 @@ public class EventRootNode extends DisplayableItemNode { } else { try { return EventNode.createEventNode(eventID, filteredEvents); - } catch (NoCurrentCaseException ex) { - //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. - LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS - return null; } catch (TskCoreException ex) { /* * Just log it: There might be lots of these errors, and we * don't want to flood the user with notifications. It will * be obvious the UI is broken anyways */ - LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Error creating explorer node for event id " + eventID + ".", ex); // NON-NLS return null; } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java index 3c56cde643..979bdf6379 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java @@ -78,7 +78,6 @@ import org.controlsfx.control.action.ActionUtils; import org.openide.awt.Actions; import org.openide.util.NbBundle; import org.openide.util.actions.Presenter; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -214,7 +213,7 @@ class ListTimeline extends BorderPane { assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS assert descriptionColumn != null : "fx:id=\"descriptionColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS - + //configure scroll controls scrollInrementComboBox.setButtonCell(new ChronoFieldListCell()); scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell()); @@ -676,14 +675,11 @@ class ListTimeline extends BorderPane { } } } - }; + } //show new context menu. new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()])) .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY()); - } catch (NoCurrentCaseException ex) { - //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. - logger.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a TimelineEvent.", ex); //NON-NLS } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a TimelineEvent.", ex); //NON-NLS Platform.runLater(() -> {