diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java index 26d9126fe0..3661d31208 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java @@ -39,69 +39,142 @@ import org.sleuthkit.datamodel.TskData; public class SingleEvent implements TimeLineEvent { private final long eventID; - private final long fileID; + /** + * The TSK object ID of the file this event is derived from. + */ + private final long objID; + + /** + * The TSK artifact ID of the file this event is derived from. Null, if this + * event is not derived from an artifact. + */ private final Long artifactID; + + /** + * The TSK datasource ID of the datasource this event belongs to. + */ private final long dataSourceID; + /** + * The time of this event in second from the Unix epoch. + */ private final long time; - private final EventType subType; + /** + * The type of this event. + */ + private final EventType type; + + /** + * The three descriptions (full, med, short) stored in a map, keyed by + * DescriptionLOD (Level of Detail) + */ private final ImmutableMap descriptions; + /** + * The known value for the file this event is derived from. + */ private final TskData.FileKnown known; + + /** + * True if the file this event is derived from hits any of the configured + * hash sets. + */ private final boolean hashHit; + + /** + * True if the file or artifact this event is derived from is tagged. + */ private final boolean tagged; /** - * Single events may or may not have their parent set, since that is a - * transient property of the current (details ) view. The parent may be any - * kind of MultiEvent. + * Single events may or may not have their parent set, since the parent is a + * transient property of the current (details) view settings. */ private MultiEvent parent = null; public SingleEvent(long eventID, long dataSourceID, long objID, @Nullable Long artifactID, long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit, boolean tagged) { this.eventID = eventID; - this.fileID = objID; + this.dataSourceID = dataSourceID; + this.objID = objID; this.artifactID = Long.valueOf(0).equals(artifactID) ? null : artifactID; this.time = time; - this.subType = type; + this.type = type; descriptions = ImmutableMap.of(DescriptionLoD.FULL, fullDescription, DescriptionLoD.MEDIUM, medDescription, DescriptionLoD.SHORT, shortDescription); - this.known = known; this.hashHit = hashHit; this.tagged = tagged; - this.dataSourceID = dataSourceID; } + /** + * Get a new SingleEvent that is the same as this event, but with the given + * parent. + * + * @param newParent the parent of the new event object. + * + * @return a new SingleEvent that is the same as this event, but with the + * given parent. + */ public SingleEvent withParent(MultiEvent newParent) { - SingleEvent singleEvent = new SingleEvent(eventID, dataSourceID, fileID, artifactID, time, subType, descriptions.get(DescriptionLoD.FULL), descriptions.get(DescriptionLoD.MEDIUM), descriptions.get(DescriptionLoD.SHORT), known, hashHit, tagged); + SingleEvent singleEvent = new SingleEvent(eventID, dataSourceID, objID, artifactID, time, type, descriptions.get(DescriptionLoD.FULL), descriptions.get(DescriptionLoD.MEDIUM), descriptions.get(DescriptionLoD.SHORT), known, hashHit, tagged); singleEvent.parent = newParent; return singleEvent; } + /** + * Is the file or artifact this event is derived from tagged? + * + * @return true if he file or artifact this event is derived from is tagged. + */ public boolean isTagged() { return tagged; } + /** + * Is the file this event is derived from in any of the configured hash + * sets. + * + * + * @return True if the file this event is derived from is in any of the + * configured hash sets. + */ public boolean isHashHit() { return hashHit; } + /** + * Get the artifact id of the artifact this event is derived from. + * + * @return An Optional containing the artifact ID. Will be empty if this + * event is not derived from an artifact + */ public Optional getArtifactID() { return Optional.ofNullable(artifactID); } + /** + * Get the event id of this event. + * + * @return The event id of this event. + */ public long getEventID() { return eventID; } + /** + * Get the obj id of the file this event is derived from. + * + * @return the object id. + */ public long getFileID() { - return fileID; + return objID; } /** - * @return the time in seconds from unix epoch + * Get the time of this event (in seconds from the Unix epoch). + * + * @return the time of this event in seconds from Unix epoch */ public long getTime() { return time; @@ -109,29 +182,61 @@ public class SingleEvent implements TimeLineEvent { @Override public EventType getEventType() { - return subType; + return type; } + /** + * Get the full description of this event. + * + * @return the full description + */ public String getFullDescription() { return getDescription(DescriptionLoD.FULL); } + /** + * Get the medium description of this event. + * + * @return the medium description + */ public String getMedDescription() { return getDescription(DescriptionLoD.MEDIUM); } + /** + * Get the short description of this event. + * + * @return the short description + */ public String getShortDescription() { return getDescription(DescriptionLoD.SHORT); } + /** + * Get the known value of the file this event is derived from. + * + * @return the known value + */ public TskData.FileKnown getKnown() { return known; } + /** + * Get the description of this event at the give level of detail(LoD). + * + * @param lod The level of detail to get. + * + * @return The description of this event at the given level of detail. + */ public String getDescription(DescriptionLoD lod) { return descriptions.get(lod); } + /** + * Get the datasource id of the datasource this event belongs to. + * + * @return the datasource id. + */ public long getDataSourceID() { return dataSourceID; } @@ -185,7 +290,7 @@ public class SingleEvent implements TimeLineEvent { @Override public SortedSet getClusters() { - EventCluster eventCluster = new EventCluster(new Interval(time * 1000, time * 1000), subType, getEventIDs(), getEventIDsWithHashHits(), getEventIDsWithTags(), getFullDescription(), DescriptionLoD.FULL); + EventCluster eventCluster = new EventCluster(new Interval(time * 1000, time * 1000), type, getEventIDs(), getEventIDsWithHashHits(), getEventIDsWithTags(), getFullDescription(), DescriptionLoD.FULL); return ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)).add(eventCluster).build(); } @@ -203,9 +308,9 @@ public class SingleEvent implements TimeLineEvent { * get the EventStripe (if any) that contains this event, skipping over any * intervening event cluster * - * @return an Optional containing the parent stripe of this cluster. is - * empty if the cluster has no parent set or the parent has no - * parent stripe. + * @return an Optional containing the parent stripe of this cluster: empty + * if the cluster has no parent set or the parent has no parent + * stripe. */ @Override public Optional getParentStripe() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java index bd26026631..2a62f8792e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java @@ -25,37 +25,98 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** - * + * An event of the timeline. Concrete implementations may represent single + * events or multiple events grouped together based on some common properties + * (for example close together in time and or having similar descriptions or + * event types). Note that for SingleEvents or events that are all simultaneous, + * the start time may be equal to the end time. */ public interface TimeLineEvent { + /** + * Get a description of this event. Implementations may choose what level of + * description to provide. + * + * @return A description of this event. + */ public String getDescription(); + /** + * Get the Description level of detail at which all single events of this + * event have the same description, ie, what level of detail was used to + * group these events. + * + * @return the description level of detail of the given events + */ public DescriptionLoD getDescriptionLoD(); /** - * get the EventStripe (if any) that contains this event + * get the EventStripe (if any) that contains this event. * * @return an Optional containing the parent stripe of this event, or is * empty if the event has no parent stripe. */ public Optional getParentStripe(); + /** + * Get the id(s) of this event as a set. + * + * @return a Set containing the event id(s) of this event. + */ Set getEventIDs(); + /** + * Get the id(s) of this event that have hash hits associated with them. + * + * @return a Set containing the event id(s) of this event that have hash + * hits associated with them. + */ Set getEventIDsWithHashHits(); + /** + * Get the id(s) of this event that have tags associated with them. + * + * @return a Set containing the event id(s) of this event that have tags + * associated with them. + */ Set getEventIDsWithTags(); + /** + * Get the EventType of this event. + * + * @return the EventType of this event. + */ EventType getEventType(); + /** + * Get the start time of this event as milliseconds from the Unix Epoch. + * + * @return the start time of this event as milliseconds from the Unix Epoch. + */ long getEndMillis(); + /** + * Get the end time of this event as milliseconds from the Unix Epoch. + * + * @return the end time of this event as milliseconds from the Unix Epoch. + */ long getStartMillis(); + /** + * Get the number of SingleEvents this event contains. + * + * @return the number of SingleEvents this event contains. + */ default int getSize() { return getEventIDs().size(); } + /** + * Get the EventClusters that make up this event. May be null for + * SingleEvents, or return a refernece to this event if it is an + * EventCluster + * + * @return The EventClusters that make up this event. + */ SortedSet getClusters(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle.properties deleted file mode 100644 index 9a701954f0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle.properties +++ /dev/null @@ -1 +0,0 @@ -EventRoodNode.tooManyNode.displayName=Too many events to display. Maximum \= {0}. But there are {1} to display. \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 686dfea1c7..6fd3693514 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,12 +39,20 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * + * Root Explorer node to represent events. */ public class EventRootNode extends DisplayableItemNode { + /** + * Since the lazy loading seems to be broken if there are more than this + * many child events, we don't show them and just show a message showing the + * number of events + */ public static final int MAX_EVENTS_TO_DISPLAY = 5000; + /** + * the number of child events + */ private final int childCount; public EventRootNode(String NAME, Collection fileIds, FilteredEventsModel filteredEvents) { @@ -80,16 +88,20 @@ public class EventRootNode extends DisplayableItemNode { // return "EventRoot"; // } /** - * The node factories used to make lists of files to send to the result - * viewer using the lazy loading (rather than background) loading option to - * facilitate + * ChildFactory for EventNodes. */ private static class EventNodeChildFactory extends ChildFactory { private static final Logger LOGGER = Logger.getLogger(EventNodeChildFactory.class.getName()); + /** + * list of event ids that act as keys for the child nodes. + */ private final Collection eventIDs; + /** + * filteredEvents is used to lookup the events from their ids + */ private final FilteredEventsModel filteredEvents; EventNodeChildFactory(Collection fileIds, FilteredEventsModel filteredEvents) { @@ -99,17 +111,31 @@ public class EventRootNode extends DisplayableItemNode { @Override protected boolean createKeys(List toPopulate) { + /** + * if there are too many events, just add one id (-1) to indicate + * this. + */ if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) { toPopulate.addAll(eventIDs); } else { - toPopulate.add(-1l); + toPopulate.add(-1L); } return true; } @Override protected Node createNodeForKey(Long eventID) { - if (eventID >= 0) { + if (eventID < 0) { + /* + * if the eventId is a the special value, return a node with a + * warning that their are too many evens + */ + return new TooManyNode(eventIDs.size()); + } else { + /* + * look up the event by id and creata an EventNode with the + * appropriate data in the lookup. + */ final SingleEvent eventById = filteredEvents.getEventById(eventID); try { SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); @@ -122,30 +148,33 @@ public class EventRootNode extends DisplayableItemNode { return new EventNode(eventById, file); } } else { + //This should never happen in normal operations LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS return null; } - } catch (IllegalStateException | TskCoreException ex) { + //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS return null; } - } else { - return new TooManyNode(eventIDs.size()); } } } + /** + * A Node that just shows a warning message that their are too many events + * to show + */ private static class TooManyNode extends AbstractNode { - public TooManyNode(int size) { + @NbBundle.Messages({ + "# {0} - maximum number of events to display", + "# {1} - the number of events that is too many", + "EventRoodNode.tooManyNode.displayName=Too many events to display. Maximum = {0}. But there are {1} to display."}) + TooManyNode(int size) { super(Children.LEAF); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/info-icon-16.png"); // NON-NLS - setDisplayName( - NbBundle.getMessage(this.getClass(), - "EventRoodNode.tooManyNode.displayName", - MAX_EVENTS_TO_DISPLAY, - size)); + setDisplayName(Bundle.EventRoodNode_tooManyNode_displayName(MAX_EVENTS_TO_DISPLAY, size)); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java index b0c4ef85d1..ef29b563af 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java @@ -483,6 +483,12 @@ public abstract class AbstractVisualizationPane the type of data displayed along the X-Axis. + */ abstract protected class VisualizationUpdateTask extends LoggedTask { protected VisualizationUpdateTask(String taskName, boolean logStateChanges) { @@ -533,7 +539,7 @@ public abstract class AbstractVisualizationPane, DetailsChart> { @@ -87,8 +86,23 @@ public class DetailViewPane extends AbstractVisualizationPane verticalAxis = new EventAxis<>(Bundle.DetailViewPane_primaryLaneLabel_text()); + /** + * ObservableList of events selected in this detail view. It is + * automatically mapped from the list of nodes selected in this view. + */ private final MappedList> selectedEvents; + /** + * Constructor for a DetailViewPane + * + * @param controller the Controller to use + * @param partPane the Pane that represents the smaller part of the + * time unit displayed on the horizontal axis + * @param contextPane the Pane that represents the larger/contextual + * part of the time unit displayed on the horizontal + * axis + * @param bottomLeftSpacer a spacer to keep everything aligned. + */ public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region bottomLeftSpacer) { super(controller, partPane, contextPane, bottomLeftSpacer); this.selectedEvents = new MappedList<>(getSelectedNodes(), EventNodeBase::getEvent); @@ -98,7 +112,7 @@ public class DetailViewPane extends AbstractVisualizationPane(new DetailViewSettingsPane(chart.getLayoutSettings()).getChildrenUnmodifiable()); -// //bind layout fo axes and spacers + //bind layout fo axes and spacers detailsChartDateAxis.getTickMarks().addListener((Observable observable) -> layoutDateLabels()); detailsChartDateAxis.getTickSpacing().addListener(observable -> layoutDateLabels()); verticalAxis.setAutoRanging(false); //prevent XYChart.updateAxisRange() from accessing dataSeries on JFX thread causing ConcurrentModificationException @@ -107,7 +121,10 @@ public class DetailViewPane extends AbstractVisualizationPane { + //update selected nodes highlight chart.setHighlightPredicate(selectedNodes::contains); + + //update controllers list of selected event ids when view's selection changes. getController().selectEventIDs(selectedNodes.stream() .flatMap(detailNode -> detailNode.getEventIDs().stream()) .collect(Collectors.toList())); @@ -115,29 +132,45 @@ public class DetailViewPane extends AbstractVisualizationPane getAllNestedEvents() { return chart.getAllNestedEvents(); } + /* + * Get a list of the events that are selected in thes view. + */ public ObservableList getSelectedEvents() { return selectedEvents; } + /** + * Observe the list of events that should be highlighted in this view. + * + * + * @param highlightedEvents the ObservableList of events that should be + * highlighted in this view. + */ public void setHighLightedEvents(ObservableList highlightedEvents) { highlightedEvents.addListener((Observable observable) -> { + /* + * build a predicate that matches events with the same description + * as any of the events in highlightedEvents or which are selected + */ Predicate> highlightPredicate = - highlightedEvents.stream() - .map(TimeLineEvent::getDescription) + highlightedEvents.stream() // => events + .map(TimeLineEvent::getDescription)// => event descriptions .map(new Function>>() { @Override public Predicate> apply(String description) { return eventNode -> StringUtils.equalsIgnoreCase(eventNode.getDescription(), description); } - }) - .reduce(selectedNodes::contains, Predicate::or); - chart.setHighlightPredicate(highlightPredicate); + })// => predicates that match strings agains the descriptions of the events in highlightedEvents + .reduce(selectedNodes::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes + chart.setHighlightPredicate(highlightPredicate); //use this predicate to highlight nodes }); } @@ -146,13 +179,28 @@ public class DetailViewPane extends AbstractVisualizationPane c1, Boolean selected) { c1.applySelectionEffect(selected); - - } - - DateTime getDateTimeForPosition(double layoutX) { - return chart.getDateTimeForPosition(layoutX); - } + /** + * A Pane that contains widgets to adjust settings specific to a + * DetailViewPane + */ static private class DetailViewSettingsPane extends HBox { @FXML @@ -234,32 +280,9 @@ public class DetailViewPane extends AbstractVisualizationPane { + final InvalidationListener sliderListener = observable -> { if (truncateWidthSlider.isValueChanging() == false) { layoutSettings.truncateWidthProperty().set(truncateWidthSlider.getValue()); } @@ -298,23 +333,15 @@ public class DetailViewPane extends AbstractVisualizationPane eventStripes = filteredEvents.getEventStripes(); - if (eventStripes.size() > 2000) { - Task task = new Task() { + //get the event stripes to be displayed + List eventStripes = filteredEvents.getEventStripes(); + final int size = eventStripes.size(); + //if there are too many stipes show a confirmation dialog + if (size > 2000) { + Task task = new Task() { @Override protected ButtonType call() throws Exception { ButtonType ContinueButtonType = new ButtonType(Bundle.DetailViewPane_loggedTask_continueButton(), ButtonBar.ButtonData.OK_DONE); ButtonType back = new ButtonType(Bundle.DetailViewPane_loggedTask_backButton(), ButtonBar.ButtonData.CANCEL_CLOSE); - Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.DetailViewPane_loggedTask_prompt(eventStripes.size()), ContinueButtonType, back); + Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.DetailViewPane_loggedTask_prompt(size), ContinueButtonType, back); alert.setHeaderText(""); alert.initModality(Modality.APPLICATION_MODAL); alert.initOwner(getScene().getWindow()); @@ -362,11 +393,14 @@ public class DetailViewPane extends AbstractVisualizationPane chart.addStripe(stripe)); } + return eventStripes.isEmpty() == false; }