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;
350 boolean showFullRange() throws TskCoreException {
351 synchronized (filteredEvents) {
352 return pushTimeRange(filteredEvents.getSpanningInterval());
363 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
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);
381 void shutDownTimeLineListeners() {
388 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
389 public
void shutDownTimeLineGui() {
390 if (topComponent != null) {
391 topComponent.close();
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 {
453 synchronized (filteredEvents) {
459 final Interval timeRange = filteredEvents.getTimeRange();
460 long toDurationMillis = timeRange.toDurationMillis() / 4;
461 DateTime start = timeRange.getStart().minus(toDurationMillis);
462 DateTime end = timeRange.getEnd().plus(toDurationMillis);
463 pushTimeRange(
new Interval(start, end));
467 final Interval timeRange = filteredEvents.getTimeRange();
468 long toDurationMillis = timeRange.toDurationMillis() / 4;
469 DateTime start = timeRange.getStart().plus(toDurationMillis);
470 DateTime end = timeRange.getEnd().minus(toDurationMillis);
471 pushTimeRange(
new Interval(start, end));
479 synchronized private
void showWindow() {
480 if (topComponent == null) {
483 if (topComponent.isOpened() ==
false) {
486 topComponent.toFront();
491 topComponent.requestActive();
500 if (currentZoom == null) {
501 advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
516 synchronized public boolean pushTimeRange(Interval timeRange)
throws TskCoreException {
518 Interval clampedTimeRange;
519 if (timeRange == null) {
520 clampedTimeRange = this.filteredEvents.getSpanningInterval();
522 Interval spanningInterval = this.filteredEvents.getSpanningInterval();
523 if (spanningInterval.overlaps(timeRange)) {
524 clampedTimeRange = spanningInterval.overlap(timeRange);
526 clampedTimeRange = spanningInterval;
531 if (currentZoom == null) {
532 advance(InitialZoomState.withTimeRange(clampedTimeRange));
534 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
554 return showFullRange();
562 if (currentZoom == null) {
563 advance(InitialZoomState.withDescrLOD(newLOD));
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) {
574 advance(InitialZoomState.withTimeAndType(overlappingTimeRange, typeZoom));
577 }
else if (currentZoom.
hasTimeRange(overlappingTimeRange) ==
false) {
586 if (currentZoom == null) {
587 advance(InitialZoomState.withFilterState(filter));
594 historyManager.advance();
598 historyManager.retreat();
602 historyManager.advance(newState);
611 final synchronized public void selectEventIDs(Collection<Long> eventIDs)
throws TskCoreException {
612 selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
613 selectedEventIDs.setAll(eventIDs);
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 {
623 return filteredEvents.getEventIDs(timeRange,
new SqlFilterState<>(
new EventTypeFilter(type),
true));
628 protected void succeeded() {
632 selectedTimeRange.set(timeRange);
633 selectedEventIDs.setAll(
get());
636 }
catch (InterruptedException | ExecutionException ex) {
637 logger.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
642 monitorTask(selectTimeAndTypeTask);
654 Platform.runLater(() -> {
657 task.stateProperty().addListener((Observable observable) -> {
658 switch (task.getState()) {
667 if (tasks.isEmpty() ==
false) {
668 taskProgress.bind(tasks.get(0).progressProperty());
669 taskMessage.bind(tasks.get(0).messageProperty());
670 taskTitle.bind(tasks.get(0).titleProperty());
676 taskProgress.bind(task.progressProperty());
677 taskMessage.bind(task.messageProperty());
678 taskTitle.bind(task.titleProperty());
679 switch (task.getState()) {
682 executor.submit(task);
691 if (tasks.isEmpty() ==
false) {
692 taskProgress.bind(tasks.get(0).progressProperty());
693 taskMessage.bind(tasks.get(0).messageProperty());
694 taskTitle.bind(tasks.get(0).titleProperty());
709 eventbus.register(listener);
718 eventbus.unregister(listener);
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:
773 future = executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
775 case BLACKBOARD_ARTIFACT_TAG_DELETED:
776 future = executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
778 case CONTENT_TAG_ADDED:
779 future = executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
781 case CONTENT_TAG_DELETED:
782 future = executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
784 case DATA_SOURCE_ADDED:
785 future = executor.submit(() -> {
786 filteredEvents.handleDataSourceAdded();
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()
static void shutDownTaskExecutor(ExecutorService executor)
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)