19package org.sleuthkit.autopsy.timeline;
21import com.google.common.eventbus.EventBus;
22import com.google.common.util.concurrent.Futures;
23import com.google.common.util.concurrent.ListenableFuture;
24import com.google.common.util.concurrent.ListeningExecutorService;
25import com.google.common.util.concurrent.MoreExecutors;
26import com.google.common.util.concurrent.ThreadFactoryBuilder;
27import java.beans.PropertyChangeEvent;
28import java.time.ZoneId;
29import java.util.Collection;
30import java.util.Collections;
31import static java.util.Collections.singleton;
32import java.util.Optional;
33import java.util.TimeZone;
34import java.util.concurrent.ExecutionException;
35import java.util.concurrent.Executors;
36import java.util.logging.Level;
37import javafx.application.Platform;
38import javafx.beans.Observable;
39import javafx.beans.property.ReadOnlyBooleanProperty;
40import javafx.beans.property.ReadOnlyDoubleProperty;
41import javafx.beans.property.ReadOnlyDoubleWrapper;
42import javafx.beans.property.ReadOnlyListProperty;
43import javafx.beans.property.ReadOnlyListWrapper;
44import javafx.beans.property.ReadOnlyObjectProperty;
45import javafx.beans.property.ReadOnlyObjectWrapper;
46import javafx.beans.property.ReadOnlyStringProperty;
47import javafx.beans.property.ReadOnlyStringWrapper;
48import javafx.collections.FXCollections;
49import javafx.collections.ObservableList;
50import javafx.collections.ObservableSet;
51import javafx.concurrent.Task;
52import static javafx.concurrent.Worker.State.FAILED;
53import static javafx.concurrent.Worker.State.SUCCEEDED;
54import javafx.scene.control.Alert;
55import javax.annotation.concurrent.GuardedBy;
56import javax.swing.SwingUtilities;
57import org.joda.time.DateTime;
58import org.joda.time.DateTimeZone;
59import org.joda.time.Interval;
60import org.joda.time.ReadablePeriod;
61import org.joda.time.format.DateTimeFormat;
62import org.joda.time.format.DateTimeFormatter;
63import org.openide.util.NbBundle;
64import org.sleuthkit.autopsy.casemodule.Case;
65import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
66import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
67import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
68import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
69import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
70import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
71import org.sleuthkit.autopsy.coreutils.History;
72import org.sleuthkit.autopsy.coreutils.LoggedTask;
73import org.sleuthkit.autopsy.coreutils.Logger;
74import org.sleuthkit.autopsy.coreutils.ThreadConfined;
75import org.sleuthkit.autopsy.coreutils.ThreadUtils;
76import org.sleuthkit.autopsy.events.AutopsyEvent;
77import org.sleuthkit.autopsy.ingest.IngestManager;
78import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
79import org.sleuthkit.autopsy.timeline.events.TimelineEventAddedEvent;
80import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
81import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailViewEvent;
82import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.SqlFilterState;
83import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.DescriptionFilterState;
84import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
85import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
86import org.sleuthkit.autopsy.timeline.zooming.TimeUnits;
87import org.sleuthkit.autopsy.timeline.zooming.EventsModelParams;
88import org.sleuthkit.datamodel.AbstractFile;
89import org.sleuthkit.datamodel.BlackboardArtifact;
90import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT;
91import org.sleuthkit.datamodel.TskCoreException;
92import org.sleuthkit.datamodel.TimelineEventType;
93import org.sleuthkit.datamodel.TimelineFilter.EventTypeFilter;
94import org.sleuthkit.datamodel.TimelineLevelOfDetail;
111@NbBundle.Messages({
"Timeline.dialogs.title= Timeline",
112 "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
113public class TimeLineController {
117 private static final ReadOnlyObjectWrapper<TimeZone>
timeZone =
new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
119 private final ListeningExecutorService
executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
120 new ThreadFactoryBuilder().setNameFormat(
"Timeline Controller BG thread").build()));
121 private final ReadOnlyListWrapper<Task<?>>
tasks =
new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
122 private final ReadOnlyDoubleWrapper
taskProgress =
new ReadOnlyDoubleWrapper(-1);
123 private final ReadOnlyStringWrapper
taskMessage =
new ReadOnlyStringWrapper();
124 private final ReadOnlyStringWrapper
taskTitle =
new ReadOnlyStringWrapper();
125 private final ReadOnlyStringWrapper
statusMessage =
new ReadOnlyStringWrapper();
127 private final EventBus
eventbus =
new EventBus(
"TimeLineController_EventBus");
134 return DateTimeFormat.forPattern(
"YYYY-MM-dd HH:mm:ss").withZone(
getJodaTimeZone());
142 return timeZone.getReadOnlyProperty();
178 synchronized public ReadOnlyListProperty<Task<?>>
getTasks() {
179 return tasks.getReadOnlyProperty();
200 @GuardedBy(
"filteredEvents")
214 private final ObservableList<Long>
selectedEventIDs = FXCollections.<Long>observableArrayList();
217 private final ReadOnlyObjectWrapper<Interval>
selectedTimeRange = new ReadOnlyObjectWrapper<>();
257 return viewMode.getReadOnlyProperty();
266 if (this.viewMode.get() !=
viewMode) {
281 this.autoCase = autoCase;
282 filteredEvents =
new EventsModel(autoCase, currentParams.getReadOnlyProperty());
289 historyManager.currentState().addListener((observable, oldState, newState) -> {
292 currentParams.set(historyManagerState);
297 InitialZoomState =
new EventsModelParams(filteredEvents.getSpanningInterval(),
298 TimelineEventType.HierarchyLevel.CATEGORY,
299 filteredEvents.eventFilterProperty().get(),
300 TimelineLevelOfDetail.LOW);
301 }
catch (TskCoreException ex) {
302 throw new TskCoreException(
"Error getting spanning interval.", ex);
304 historyManager.advance(InitialZoomState);
307 viewMode.addListener(observable -> {
309 selectEventIDs(Collections.emptySet());
310 }
catch (TskCoreException ex) {
311 logger.log(Level.SEVERE,
"Error clearing the timeline selection.", ex);
332 private final ObservableSet<DetailViewEvent>
pinnedEvents = FXCollections.observableSet();
350 boolean showFullRange() throws TskCoreException {
351 synchronized (filteredEvents) {
352 return pushTimeRange(filteredEvents.getSpanningInterval());
363 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
372 }
catch (TskCoreException ex) {
373 throw new TskCoreException(
"Error pushing requested timerange.", ex);
381 void shutDownTimeLineListeners() {
388 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
405 void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
406 Platform.runLater(() -> {
408 if (promptDialogManager.bringCurrentDialogToFront()) {
413 IngestManager.getInstance().isIngestRunning()
414 && promptDialogManager.confirmDuringIngest() ==
false) {
418 if (file ==
null && artifact ==
null) {
419 SwingUtilities.invokeLater(TimeLineController.this::showWindow);
422 ShowInTimelineDialog showInTimelineDilaog = (file ==
null)
423 ?
new ShowInTimelineDialog(
this, artifact)
424 :
new ShowInTimelineDialog(
this, file);
425 Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
426 dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
427 SwingUtilities.invokeLater(this::showWindow);
429 showInListView(viewInTimelineRequestedEvent);
430 }
catch (TskCoreException ex) {
431 logger.log(Level.SEVERE,
"Error showing requested events in listview: " + viewInTimelineRequestedEvent, ex);
432 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
437 }
catch (TskCoreException tskCoreException) {
438 logger.log(Level.SEVERE,
"Error showing Timeline ", tskCoreException);
439 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
452 synchronized public void pushPeriod(ReadablePeriod period)
throws TskCoreException {
460 long toDurationMillis = timeRange.toDurationMillis() / 4;
461 DateTime start = timeRange.getStart().minus(toDurationMillis);
462 DateTime end = timeRange.getEnd().plus(toDurationMillis);
468 long toDurationMillis = timeRange.toDurationMillis() / 4;
469 DateTime start = timeRange.getStart().plus(toDurationMillis);
470 DateTime end = timeRange.getEnd().minus(toDurationMillis);
500 if (currentZoom ==
null) {
516 synchronized public boolean pushTimeRange(Interval timeRange)
throws TskCoreException {
518 Interval clampedTimeRange;
519 if (timeRange ==
null) {
522 Interval spanningInterval = this.
filteredEvents.getSpanningInterval();
523 if (spanningInterval.overlaps(timeRange)) {
524 clampedTimeRange = spanningInterval.overlap(timeRange);
526 clampedTimeRange = spanningInterval;
531 if (currentZoom ==
null) {
534 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
554 return showFullRange();
562 if (currentZoom ==
null) {
564 }
else if (currentZoom.
hasDescrLOD(newLOD) ==
false) {
569 @SuppressWarnings(
"AssignmentToMethodParameter")
570 synchronized public
void pushTimeAndType(Interval timeRange, TimelineEventType.HierarchyLevel typeZoom) throws TskCoreException {
571 Interval overlappingTimeRange = this.
filteredEvents.getSpanningInterval().overlap(timeRange);
573 if (currentZoom ==
null) {
576 advance(currentZoom.withTimeAndType(overlappingTimeRange, typeZoom));
577 }
else if (currentZoom.
hasTimeRange(overlappingTimeRange) ==
false) {
578 advance(currentZoom.withTimeRange(overlappingTimeRange));
580 advance(currentZoom.withTypeZoomLevel(typeZoom));
586 if (currentZoom ==
null) {
611 final synchronized public void selectEventIDs(Collection<Long> eventIDs)
throws TskCoreException {
616 public void selectTimeAndType(Interval interval, TimelineEventType type)
throws TskCoreException {
617 final Interval timeRange =
filteredEvents.getSpanningInterval().overlap(interval);
621 protected Collection< Long> call()
throws Exception {
622 synchronized (TimeLineController.this) {
628 protected void succeeded() {
631 synchronized (TimeLineController.this) {
636 }
catch (InterruptedException | ExecutionException ex) {
637 logger.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
654 Platform.runLater(() -> {
657 task.stateProperty().addListener((Observable observable) -> {
658 switch (task.getState()) {
667 if (
tasks.isEmpty() ==
false) {
679 switch (task.getState()) {
691 if (
tasks.isEmpty() ==
false) {
722 TimeLineController.timeZone.set(
timeZone);
726 void handleIngestModuleEvent(PropertyChangeEvent evt) {
734 }
catch (NoCurrentCaseException notUsed) {
740 if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
744 switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
745 case CONTENT_CHANGED:
749 ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
750 if (
null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_HASHSET_HIT.getTypeID()) {
751 logFutureException(executor.submit(() -> filteredEvents.updateEventsForHashSetHits(eventData.getArtifacts())),
752 "Error executing task in response to DATA_ADDED event.",
753 "Error executing response to new data.");
769 void handleCaseEvent(PropertyChangeEvent evt) {
770 ListenableFuture<?> future = Futures.immediateFuture(
null);
771 switch (
Case.
Events.valueOf(evt.getPropertyName())) {
772 case BLACKBOARD_ARTIFACT_TAG_ADDED:
775 case BLACKBOARD_ARTIFACT_TAG_DELETED:
778 case CONTENT_TAG_ADDED:
779 future = executor.submit(() -> filteredEvents.handleContentTagAdded((
ContentTagAddedEvent) evt));
781 case CONTENT_TAG_DELETED:
784 case DATA_SOURCE_ADDED:
785 future = executor.submit(() -> {
786 filteredEvents.handleDataSourceAdded();
790 case TIMELINE_EVENT_ADDED:
791 future = executor.submit(() -> {
797 logFutureException(future,
798 "Error executing task in response to " + evt.getPropertyName() +
" event.",
799 "Error executing task in response to case event.");
802 private void logFutureException(ListenableFuture<?> future, String errorLogMessage, String errorUserMessage) {
803 future.addListener(() -> {
806 }
catch (InterruptedException | ExecutionException ex) {
807 logger.log(Level.SEVERE, errorLogMessage, ex);
809 }, MoreExecutors.directExecutor());
static Case getCurrentCaseThrows()
synchronized static Logger getLogger(String name)
static void shutDownTaskExecutor(ExecutorService executor)
final ReadOnlyObjectWrapper< Interval > selectedTimeRange
synchronized ReadOnlyBooleanProperty canAdvanceProperty()
static DateTimeZone getJodaTimeZone()
void pinEvent(DetailViewEvent event)
void shutDownTimeLineGui()
synchronized void pushPeriod(ReadablePeriod period)
synchronized Interval getSelectedTimeRange()
synchronized void unRegisterForEvents(Object listener)
synchronized TimeLineTopComponent getTopComponent()
TimeLineTopComponent topComponent
void unPinEvent(DetailViewEvent event)
final History< EventsModelParams > historyManager
synchronized void pushZoomOutTime()
final ReadOnlyListWrapper< Task<?> > tasks
synchronized void pushEventTypeZoom(TimelineEventType.HierarchyLevel typeZoomeLevel)
final EventsModelParams InitialZoomState
final ReadOnlyStringWrapper taskMessage
static TimeZone getTimeZone()
ObservableSet< DetailViewEvent > getPinnedEvents()
final synchronized void selectEventIDs(Collection< Long > eventIDs)
synchronized ReadOnlyStringProperty taskMessageProperty()
final EventsModel filteredEvents
EventsModel getEventsModel()
synchronized void advance()
ObservableList< DescriptionFilterState > getQuickHideFilters()
static final ReadOnlyObjectWrapper< TimeZone > timeZone
synchronized ReadOnlyStringProperty taskTitleProperty()
void showInListView(ViewInTimelineRequestedEvent requestEvent)
synchronized boolean pushTimeUnit(TimeUnits timeUnit)
void selectTimeAndType(Interval interval, TimelineEventType type)
final ObservableList< DescriptionFilterState > quickHideFilters
final ReadOnlyObjectWrapper< ViewMode > viewMode
synchronized void retreat()
void applyDefaultFilters()
final ObservableSet< DetailViewEvent > pinnedEvents
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
void logFutureException(ListenableFuture<?> future, String errorLogMessage, String errorUserMessage)
final ListeningExecutorService executor
final ReadOnlyDoubleWrapper taskProgress
synchronized void pushFilters(RootFilterState filter)
final PromptDialogManager promptDialogManager
synchronized boolean pushTimeRange(Interval timeRange)
synchronized void showWindow()
final ReadOnlyStringWrapper taskTitle
synchronized ViewMode getViewMode()
void setStatusMessage(String string)
final ReadOnlyStringWrapper statusMessage
synchronized void advance(EventsModelParams newState)
synchronized void registerForEvents(Object listener)
static ZoneId getTimeZoneID()
synchronized void monitorTask(final Task<?> task)
synchronized ReadOnlyObjectProperty< Interval > selectedTimeRangeProperty()
synchronized void pushZoomInTime()
synchronized ObservableList< Long > getSelectedEventIDs()
static ReadOnlyObjectProperty< TimeZone > timeZoneProperty()
synchronized void setViewMode(ViewMode viewMode)
final ReadOnlyObjectWrapper< EventsModelParams > currentParams
synchronized void pushTimeAndType(Interval timeRange, TimelineEventType.HierarchyLevel typeZoom)
synchronized ReadOnlyListProperty< Task<?> > getTasks()
static final Logger logger
synchronized ReadOnlyDoubleProperty taskProgressProperty()
ReadOnlyStringProperty statusMessageProperty()
static DateTimeFormatter getZonedFormatter()
static synchronized void setTimeZone(TimeZone timeZone)
synchronized void pushDescrLOD(TimelineLevelOfDetail newLOD)
final ObservableList< Long > selectedEventIDs
final ObservableSet< DetailViewEvent > pinnedEventsUnmodifiable
synchronized ReadOnlyBooleanProperty canRetreatProperty()
static Interval getIntervalAroundMiddle(Interval interval, ReadablePeriod period)
RootFilterState getEventFilterState()
EventsModelParams withTypeZoomLevel(TimelineEventType.HierarchyLevel zoomLevel)
EventsModelParams withTimeRange(Interval timeRange)
boolean hasTimeRange(Interval timeRange)
EventsModelParams withDescrLOD(TimelineLevelOfDetail descrLOD)
boolean hasFilterState(RootFilterState filterSet)
boolean hasDescrLOD(TimelineLevelOfDetail newLOD)
boolean hasTypeZoomLevel(TimelineEventType.HierarchyLevel typeZoom)
EventsModelParams withFilterState(RootFilterState filter)