19 package org.sleuthkit.autopsy.timeline;
21 import com.google.common.eventbus.EventBus;
22 import com.google.common.util.concurrent.Futures;
23 import com.google.common.util.concurrent.ListenableFuture;
24 import com.google.common.util.concurrent.ListeningExecutorService;
25 import com.google.common.util.concurrent.MoreExecutors;
26 import com.google.common.util.concurrent.ThreadFactoryBuilder;
27 import java.beans.PropertyChangeEvent;
28 import java.time.ZoneId;
29 import java.util.Collection;
30 import java.util.Collections;
31 import static java.util.Collections.singleton;
32 import java.util.Optional;
33 import java.util.TimeZone;
34 import java.util.concurrent.ExecutionException;
35 import java.util.concurrent.Executors;
36 import java.util.logging.Level;
37 import javafx.application.Platform;
38 import javafx.beans.Observable;
39 import javafx.beans.property.ReadOnlyBooleanProperty;
40 import javafx.beans.property.ReadOnlyDoubleProperty;
41 import javafx.beans.property.ReadOnlyDoubleWrapper;
42 import javafx.beans.property.ReadOnlyListProperty;
43 import javafx.beans.property.ReadOnlyListWrapper;
44 import javafx.beans.property.ReadOnlyObjectProperty;
45 import javafx.beans.property.ReadOnlyObjectWrapper;
46 import javafx.beans.property.ReadOnlyStringProperty;
47 import javafx.beans.property.ReadOnlyStringWrapper;
48 import javafx.collections.FXCollections;
49 import javafx.collections.ObservableList;
50 import javafx.collections.ObservableSet;
51 import javafx.concurrent.Task;
52 import static javafx.concurrent.Worker.State.FAILED;
53 import static javafx.concurrent.Worker.State.SUCCEEDED;
54 import javafx.scene.control.Alert;
55 import javax.annotation.concurrent.GuardedBy;
56 import javax.swing.SwingUtilities;
57 import org.joda.time.DateTime;
58 import org.joda.time.DateTimeZone;
59 import org.joda.time.Interval;
60 import org.joda.time.ReadablePeriod;
61 import org.joda.time.format.DateTimeFormat;
62 import org.joda.time.format.DateTimeFormatter;
63 import org.openide.util.NbBundle;
90 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT;
93 import org.
sleuthkit.datamodel.TimelineFilter.EventTypeFilter;
111 @NbBundle.Messages({
"Timeline.dialogs.title= Timeline",
112 "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
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");
130 return timeZone.get().toZoneId();
134 return DateTimeFormat.forPattern(
"YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone());
138 return DateTimeZone.forTimeZone(timeZoneProperty().
get());
142 return timeZone.getReadOnlyProperty();
146 return timeZone.get();
156 return statusMessage.getReadOnlyProperty();
160 statusMessage.set(
string);
168 return quickHideFilters;
178 synchronized public ReadOnlyListProperty<Task<?>>
getTasks() {
179 return tasks.getReadOnlyProperty();
183 return taskProgress.getReadOnlyProperty();
187 return taskMessage.getReadOnlyProperty();
191 return taskTitle.getReadOnlyProperty();
198 private final ReadOnlyObjectWrapper<
ViewMode> viewMode = new ReadOnlyObjectWrapper<>(
ViewMode.COUNTS);
200 @GuardedBy("filteredEvents")
210 private final ReadOnlyObjectWrapper<
EventsModelParams> currentParams = new ReadOnlyObjectWrapper<>();
214 private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
217 private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
226 synchronized public ObservableList<Long> getSelectedEventIDs() {
227 return selectedEventIDs;
236 return selectedTimeRange.getReadOnlyProperty();
245 return selectedTimeRange.get();
249 return historyManager.getCanAdvance();
253 return historyManager.getCanRetreat();
257 return viewMode.getReadOnlyProperty();
266 if (this.viewMode.get() != viewMode) {
267 this.viewMode.set(viewMode);
277 return viewMode.get();
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);
320 return filteredEvents;
324 pushFilters(filteredEvents.getDefaultEventFilterState());
328 Interval boundingEventsInterval = filteredEvents.getSpanningInterval(getJodaTimeZone());
329 advance(filteredEvents.modelParamsProperty().get().withTimeRange(boundingEventsInterval));
332 private final ObservableSet<DetailViewEvent> pinnedEvents = FXCollections.observableSet();
333 private final ObservableSet<DetailViewEvent> pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents);
336 pinnedEvents.add(event);
340 pinnedEvents.removeIf(event::equals);
344 return pinnedEventsUnmodifiable;
351 synchronized (filteredEvents) {
352 return pushTimeRange(filteredEvents.getSpanningInterval());
365 synchronized (filteredEvents) {
367 selectEventIDs(requestEvent.getEventIDs());
369 if (pushTimeRange(requestEvent.getInterval()) ==
false) {
370 eventbus.post(requestEvent);
372 }
catch (TskCoreException ex) {
373 throw new TskCoreException(
"Error pushing requested timerange.", ex);
383 public
void shutDownTimeLine() {
384 if (topComponent != null) {
385 topComponent.close();
399 void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
400 Platform.runLater(() -> {
402 if (promptDialogManager.bringCurrentDialogToFront()) {
407 IngestManager.getInstance().isIngestRunning()
408 && promptDialogManager.confirmDuringIngest() ==
false) {
412 if (file == null && artifact == null) {
413 SwingUtilities.invokeLater(TimeLineController.this::showWindow);
414 this.showFullRange();
418 ShowInTimelineDialog showInTimelineDilaog = (file == null)
419 ?
new ShowInTimelineDialog(
this, artifact)
420 :
new ShowInTimelineDialog(
this, file);
421 Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
422 dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
423 SwingUtilities.invokeLater(this::showWindow);
425 showInListView(viewInTimelineRequestedEvent);
426 }
catch (TskCoreException ex) {
427 logger.log(Level.SEVERE,
"Error showing requested events in listview: " + viewInTimelineRequestedEvent, ex);
428 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
433 }
catch (TskCoreException tskCoreException) {
434 logger.log(Level.SEVERE,
"Error showing Timeline ", tskCoreException);
435 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
448 synchronized public void pushPeriod(ReadablePeriod period)
throws TskCoreException {
449 synchronized (filteredEvents) {
455 final Interval timeRange = filteredEvents.getTimeRange();
456 long toDurationMillis = timeRange.toDurationMillis() / 4;
457 DateTime start = timeRange.getStart().minus(toDurationMillis);
458 DateTime end = timeRange.getEnd().plus(toDurationMillis);
459 pushTimeRange(
new Interval(start, end));
463 final Interval timeRange = filteredEvents.getTimeRange();
464 long toDurationMillis = timeRange.toDurationMillis() / 4;
465 DateTime start = timeRange.getStart().plus(toDurationMillis);
466 DateTime end = timeRange.getEnd().minus(toDurationMillis);
467 pushTimeRange(
new Interval(start, end));
475 synchronized private
void showWindow() {
476 if (topComponent == null) {
479 if (topComponent.isOpened() ==
false) {
482 topComponent.toFront();
487 topComponent.requestActive();
496 if (currentZoom == null) {
497 advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
512 synchronized public boolean pushTimeRange(Interval timeRange)
throws TskCoreException {
514 Interval clampedTimeRange;
515 if (timeRange == null) {
516 clampedTimeRange = this.filteredEvents.getSpanningInterval();
518 Interval spanningInterval = this.filteredEvents.getSpanningInterval();
519 if (spanningInterval.overlaps(timeRange)) {
520 clampedTimeRange = spanningInterval.overlap(timeRange);
522 clampedTimeRange = spanningInterval;
527 if (currentZoom == null) {
528 advance(InitialZoomState.withTimeRange(clampedTimeRange));
530 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
550 return showFullRange();
558 if (currentZoom == null) {
559 advance(InitialZoomState.withDescrLOD(newLOD));
560 }
else if (currentZoom.
hasDescrLOD(newLOD) ==
false) {
565 @SuppressWarnings(
"AssignmentToMethodParameter")
566 synchronized public
void pushTimeAndType(Interval timeRange, TimelineEventType.HierarchyLevel typeZoom) throws TskCoreException {
567 Interval overlappingTimeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
569 if (currentZoom == null) {
570 advance(InitialZoomState.withTimeAndType(overlappingTimeRange, typeZoom));
573 }
else if (currentZoom.
hasTimeRange(overlappingTimeRange) ==
false) {
582 if (currentZoom == null) {
583 advance(InitialZoomState.withFilterState(filter));
590 historyManager.advance();
594 historyManager.retreat();
598 historyManager.advance(newState);
607 final synchronized public void selectEventIDs(Collection<Long> eventIDs)
throws TskCoreException {
608 selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
609 selectedEventIDs.setAll(eventIDs);
612 public void selectTimeAndType(Interval interval, TimelineEventType type)
throws TskCoreException {
613 final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
617 protected Collection< Long> call()
throws Exception {
619 return filteredEvents.getEventIDs(timeRange,
new SqlFilterState<>(
new EventTypeFilter(type),
true));
624 protected void succeeded() {
628 selectedTimeRange.set(timeRange);
629 selectedEventIDs.setAll(
get());
632 }
catch (InterruptedException | ExecutionException ex) {
633 logger.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
638 monitorTask(selectTimeAndTypeTask);
650 Platform.runLater(() -> {
653 task.stateProperty().addListener((Observable observable) -> {
654 switch (task.getState()) {
663 if (tasks.isEmpty() ==
false) {
664 taskProgress.bind(tasks.get(0).progressProperty());
665 taskMessage.bind(tasks.get(0).messageProperty());
666 taskTitle.bind(tasks.get(0).titleProperty());
672 taskProgress.bind(task.progressProperty());
673 taskMessage.bind(task.messageProperty());
674 taskTitle.bind(task.titleProperty());
675 switch (task.getState()) {
678 executor.submit(task);
687 if (tasks.isEmpty() ==
false) {
688 taskProgress.bind(tasks.get(0).progressProperty());
689 taskMessage.bind(tasks.get(0).messageProperty());
690 taskTitle.bind(tasks.get(0).titleProperty());
705 eventbus.register(listener);
714 eventbus.unregister(listener);
722 void handleIngestModuleEvent(PropertyChangeEvent evt) {
730 }
catch (NoCurrentCaseException notUsed) {
736 if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
740 switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
741 case CONTENT_CHANGED:
745 ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
746 if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_HASHSET_HIT.getTypeID()) {
747 logFutureException(executor.submit(() -> filteredEvents.updateEventsForHashSetHits(eventData.getArtifacts())),
748 "Error executing task in response to DATA_ADDED event.",
749 "Error executing response to new data.");
765 void handleCaseEvent(PropertyChangeEvent evt) {
766 ListenableFuture<?> future = Futures.immediateFuture(null);
767 switch (Case.Events.valueOf(evt.getPropertyName())) {
768 case BLACKBOARD_ARTIFACT_TAG_ADDED:
769 future = executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
771 case BLACKBOARD_ARTIFACT_TAG_DELETED:
772 future = executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
774 case CONTENT_TAG_ADDED:
775 future = executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
777 case CONTENT_TAG_DELETED:
778 future = executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
782 SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
784 case DATA_SOURCE_ADDED:
785 future = executor.submit(() -> {
786 filteredEvents.invalidateCaches(null);
790 case TIMELINE_EVENT_ADDED:
791 future = executor.submit(() -> {
792 filteredEvents.invalidateCaches(singleton(((TimelineEventAddedEvent) evt).getAddedEventID()));
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());
synchronized ReadOnlyDoubleProperty taskProgressProperty()
synchronized void pushZoomInTime()
void unPinEvent(DetailViewEvent event)
EventsModelParams withTimeAndType(Interval timeRange, TimelineEventType.HierarchyLevel zoomLevel)
EventsModelParams withTimeRange(Interval timeRange)
static final ReadOnlyObjectWrapper< TimeZone > timeZone
synchronized ViewMode getViewMode()
void logFutureException(ListenableFuture<?> future, String errorLogMessage, String errorUserMessage)
static TimeZone getTimeZone()
synchronized void setViewMode(ViewMode viewMode)
synchronized ReadOnlyObjectProperty< Interval > selectedTimeRangeProperty()
RootFilterState getEventFilterState()
synchronized boolean pushTimeUnit(TimeUnits timeUnit)
synchronized boolean pushTimeRange(Interval timeRange)
synchronized ReadOnlyStringProperty taskTitleProperty()
synchronized void unRegisterForEvents(Object listener)
static ReadOnlyObjectProperty< TimeZone > timeZoneProperty()
boolean hasDescrLOD(TimelineLevelOfDetail newLOD)
synchronized Interval getSelectedTimeRange()
synchronized ReadOnlyStringProperty taskMessageProperty()
EventsModelParams withTypeZoomLevel(TimelineEventType.HierarchyLevel zoomLevel)
void applyDefaultFilters()
static Interval getIntervalAroundMiddle(Interval interval, ReadablePeriod period)
synchronized void pushEventTypeZoom(TimelineEventType.HierarchyLevel typeZoomeLevel)
static ZoneId getTimeZoneID()
synchronized void registerForEvents(Object listener)
static synchronized void setTimeZone(TimeZone timeZone)
final synchronized void selectEventIDs(Collection< Long > eventIDs)
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
synchronized void pushFilters(RootFilterState filter)
boolean hasTypeZoomLevel(TimelineEventType.HierarchyLevel typeZoom)
EventsModelParams withDescrLOD(TimelineLevelOfDetail descrLOD)
synchronized void advance(EventsModelParams newState)
boolean hasTimeRange(Interval timeRange)
ReadOnlyStringProperty statusMessageProperty()
synchronized TimeLineTopComponent getTopComponent()
synchronized void retreat()
static DateTimeZone getJodaTimeZone()
synchronized void advance()
EventsModel getEventsModel()
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
synchronized ReadOnlyBooleanProperty canAdvanceProperty()
EventsModelParams withFilterState(RootFilterState filter)
synchronized void pushZoomOutTime()
static DateTimeFormatter getZonedFormatter()
void pinEvent(DetailViewEvent event)
ObservableSet< DetailViewEvent > getPinnedEvents()
synchronized ReadOnlyListProperty< Task<?> > getTasks()
synchronized ReadOnlyBooleanProperty canRetreatProperty()
synchronized void pushDescrLOD(TimelineLevelOfDetail newLOD)
boolean hasFilterState(RootFilterState filterSet)
void selectTimeAndType(Interval interval, TimelineEventType type)
void setStatusMessage(String string)