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;
91 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT;
95 import org.
sleuthkit.datamodel.TimelineFilter.EventTypeFilter;
112 @NbBundle.Messages({
"Timeline.dialogs.title= Timeline",
113 "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
118 private static final ReadOnlyObjectWrapper<TimeZone> timeZone =
new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
120 private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
121 new ThreadFactoryBuilder().setNameFormat(
"Timeline Controller BG thread").build()));
122 private final ReadOnlyListWrapper<Task<?>> tasks =
new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
123 private final ReadOnlyDoubleWrapper taskProgress =
new ReadOnlyDoubleWrapper(-1);
124 private final ReadOnlyStringWrapper taskMessage =
new ReadOnlyStringWrapper();
125 private final ReadOnlyStringWrapper taskTitle =
new ReadOnlyStringWrapper();
126 private final ReadOnlyStringWrapper statusMessage =
new ReadOnlyStringWrapper();
128 private final EventBus eventbus =
new EventBus(
"TimeLineController_EventBus");
131 return timeZone.get().toZoneId();
135 return DateTimeFormat.forPattern(
"YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone());
139 return DateTimeZone.forTimeZone(timeZoneProperty().
get());
143 return timeZone.getReadOnlyProperty();
147 return timeZone.get();
157 return statusMessage.getReadOnlyProperty();
161 statusMessage.set(
string);
169 return quickHideFilters;
179 synchronized public ReadOnlyListProperty<Task<?>>
getTasks() {
180 return tasks.getReadOnlyProperty();
184 return taskProgress.getReadOnlyProperty();
188 return taskMessage.getReadOnlyProperty();
192 return taskTitle.getReadOnlyProperty();
199 private final ReadOnlyObjectWrapper<
ViewMode> viewMode = new ReadOnlyObjectWrapper<>(
ViewMode.COUNTS);
201 @GuardedBy("filteredEvents")
211 private final ReadOnlyObjectWrapper<
ZoomState> currentParams = new ReadOnlyObjectWrapper<>();
215 private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
218 private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
227 synchronized public ObservableList<Long> getSelectedEventIDs() {
228 return selectedEventIDs;
237 return selectedTimeRange.getReadOnlyProperty();
246 return selectedTimeRange.get();
250 return historyManager.getCanAdvance();
254 return historyManager.getCanRetreat();
258 return viewMode.getReadOnlyProperty();
267 if (this.viewMode.get() != viewMode) {
268 this.viewMode.set(viewMode);
278 return viewMode.get();
282 this.autoCase = autoCase;
290 historyManager.currentState().addListener((observable, oldState, newState) -> {
291 ZoomState historyManagerState = newState;
293 currentParams.set(historyManagerState);
298 InitialZoomState =
new ZoomState(filteredEvents.getSpanningInterval(),
299 TimelineEventType.TypeLevel.BASE_TYPE,
300 filteredEvents.filterProperty().get(),
301 TimelineEvent.DescriptionLevel.SHORT);
302 }
catch (TskCoreException ex) {
303 throw new TskCoreException(
"Error getting spanning interval.", ex);
305 historyManager.advance(InitialZoomState);
308 viewMode.addListener(observable -> {
310 selectEventIDs(Collections.emptySet());
311 }
catch (TskCoreException ex) {
312 logger.log(Level.SEVERE,
"Error clearing the timeline selection.", ex);
321 return filteredEvents;
325 pushFilters(filteredEvents.getDefaultFilter());
329 Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval(getJodaTimeZone());
330 advance(filteredEvents.zoomStateProperty().get().withTimeRange(boundingEventsInterval));
333 private final ObservableSet<DetailViewEvent> pinnedEvents = FXCollections.observableSet();
334 private final ObservableSet<DetailViewEvent> pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents);
337 pinnedEvents.add(event);
341 pinnedEvents.removeIf(event::equals);
345 return pinnedEventsUnmodifiable;
352 synchronized (filteredEvents) {
353 return pushTimeRange(filteredEvents.getSpanningInterval());
366 synchronized (filteredEvents) {
368 selectEventIDs(requestEvent.getEventIDs());
370 if (pushTimeRange(requestEvent.getInterval()) ==
false) {
371 eventbus.post(requestEvent);
373 }
catch (TskCoreException ex) {
374 throw new TskCoreException(
"Error pushing requested timerange.", ex);
384 public
void shutDownTimeLine() {
385 if (topComponent != null) {
386 topComponent.close();
400 void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
401 Platform.runLater(() -> {
403 if (promptDialogManager.bringCurrentDialogToFront()) {
408 IngestManager.getInstance().isIngestRunning()
409 && promptDialogManager.confirmDuringIngest() ==
false) {
413 if (file == null && artifact == null) {
414 SwingUtilities.invokeLater(TimeLineController.this::showWindow);
415 this.showFullRange();
419 ShowInTimelineDialog showInTimelineDilaog = (file == null)
420 ?
new ShowInTimelineDialog(
this, artifact)
421 :
new ShowInTimelineDialog(
this, file);
422 Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
423 dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
424 SwingUtilities.invokeLater(this::showWindow);
426 showInListView(viewInTimelineRequestedEvent);
427 }
catch (TskCoreException ex) {
428 logger.log(Level.SEVERE,
"Error showing requested events in listview: " + viewInTimelineRequestedEvent, ex);
429 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
434 }
catch (TskCoreException tskCoreException) {
435 logger.log(Level.SEVERE,
"Error showing Timeline ", tskCoreException);
436 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
449 synchronized public void pushPeriod(ReadablePeriod period)
throws TskCoreException {
450 synchronized (filteredEvents) {
456 final Interval timeRange = filteredEvents.getTimeRange();
457 long toDurationMillis = timeRange.toDurationMillis() / 4;
458 DateTime start = timeRange.getStart().minus(toDurationMillis);
459 DateTime end = timeRange.getEnd().plus(toDurationMillis);
460 pushTimeRange(
new Interval(start, end));
464 final Interval timeRange = filteredEvents.getTimeRange();
465 long toDurationMillis = timeRange.toDurationMillis() / 4;
466 DateTime start = timeRange.getStart().plus(toDurationMillis);
467 DateTime end = timeRange.getEnd().minus(toDurationMillis);
468 pushTimeRange(
new Interval(start, end));
476 synchronized private
void showWindow() {
477 if (topComponent == null) {
480 if (topComponent.isOpened() ==
false) {
483 topComponent.toFront();
488 topComponent.requestActive();
496 ZoomState currentZoom = filteredEvents.zoomStateProperty().get();
497 if (currentZoom == null) {
498 advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
513 synchronized public boolean pushTimeRange(Interval timeRange)
throws TskCoreException {
515 Interval clampedTimeRange;
516 if (timeRange == null) {
517 clampedTimeRange = this.filteredEvents.getSpanningInterval();
519 Interval spanningInterval = this.filteredEvents.getSpanningInterval();
520 if (spanningInterval.overlaps(timeRange)) {
521 clampedTimeRange = spanningInterval.overlap(timeRange);
523 clampedTimeRange = spanningInterval;
527 ZoomState currentZoom = filteredEvents.zoomStateProperty().get();
528 if (currentZoom == null) {
529 advance(InitialZoomState.withTimeRange(clampedTimeRange));
531 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
551 return showFullRange();
557 synchronized public void pushDescrLOD(TimelineEvent.DescriptionLevel newLOD) {
558 ZoomState currentZoom = filteredEvents.zoomStateProperty().get();
559 if (currentZoom == null) {
560 advance(InitialZoomState.withDescrLOD(newLOD));
561 }
else if (currentZoom.
hasDescrLOD(newLOD) ==
false) {
566 @SuppressWarnings(
"AssignmentToMethodParameter")
567 synchronized public
void pushTimeAndType(Interval timeRange, TimelineEventType.TypeLevel typeZoom) throws TskCoreException {
568 Interval overlappingTimeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
569 ZoomState currentZoom = filteredEvents.zoomStateProperty().get();
570 if (currentZoom == null) {
571 advance(InitialZoomState.withTimeAndType(overlappingTimeRange, typeZoom));
574 }
else if (currentZoom.
hasTimeRange(overlappingTimeRange) ==
false) {
582 ZoomState currentZoom = filteredEvents.zoomStateProperty().get();
583 if (currentZoom == null) {
584 advance(InitialZoomState.withFilterState(filter));
591 historyManager.advance();
595 historyManager.retreat();
599 historyManager.advance(newState);
608 final synchronized public void selectEventIDs(Collection<Long> eventIDs)
throws TskCoreException {
609 selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
610 selectedEventIDs.setAll(eventIDs);
613 public void selectTimeAndType(Interval interval, TimelineEventType type)
throws TskCoreException {
614 final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
618 protected Collection< Long> call()
throws Exception {
620 return filteredEvents.getEventIDs(timeRange,
new SqlFilterState<>(
new EventTypeFilter(type),
true));
625 protected void succeeded() {
629 selectedTimeRange.set(timeRange);
630 selectedEventIDs.setAll(
get());
633 }
catch (InterruptedException | ExecutionException ex) {
634 logger.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
639 monitorTask(selectTimeAndTypeTask);
651 Platform.runLater(() -> {
654 task.stateProperty().addListener((Observable observable) -> {
655 switch (task.getState()) {
664 if (tasks.isEmpty() ==
false) {
665 taskProgress.bind(tasks.get(0).progressProperty());
666 taskMessage.bind(tasks.get(0).messageProperty());
667 taskTitle.bind(tasks.get(0).titleProperty());
673 taskProgress.bind(task.progressProperty());
674 taskMessage.bind(task.messageProperty());
675 taskTitle.bind(task.titleProperty());
676 switch (task.getState()) {
679 executor.submit(task);
688 if (tasks.isEmpty() ==
false) {
689 taskProgress.bind(tasks.get(0).progressProperty());
690 taskMessage.bind(tasks.get(0).messageProperty());
691 taskTitle.bind(tasks.get(0).titleProperty());
706 eventbus.register(listener);
715 eventbus.unregister(listener);
723 void handleIngestModuleEvent(PropertyChangeEvent evt) {
731 }
catch (NoCurrentCaseException notUsed) {
737 if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
741 switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
742 case CONTENT_CHANGED:
746 ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
747 if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_HASHSET_HIT.getTypeID()) {
748 logFutureException(executor.submit(() -> filteredEvents.setHashHit(eventData.getArtifacts(),
true)),
749 "Error executing task in response to DATA_ADDED event.",
750 "Error executing response to new data.");
766 void handleCaseEvent(PropertyChangeEvent evt) {
767 ListenableFuture<?> future = Futures.immediateFuture(null);
768 switch (Case.Events.valueOf(evt.getPropertyName())) {
769 case BLACKBOARD_ARTIFACT_TAG_ADDED:
770 future = executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
772 case BLACKBOARD_ARTIFACT_TAG_DELETED:
773 future = executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
775 case CONTENT_TAG_ADDED:
776 future = executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
778 case CONTENT_TAG_DELETED:
779 future = executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
783 SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
785 case DATA_SOURCE_ADDED:
786 future = executor.submit(() -> {
787 filteredEvents.invalidateCaches(null);
791 case TIMELINE_EVENT_ADDED:
792 future = executor.submit(() -> {
793 filteredEvents.invalidateCaches(singleton(((TimelineEventAddedEvent) evt).getAddedEventID()));
798 logFutureException(future,
799 "Error executing task in response to " + evt.getPropertyName() +
" event.",
800 "Error executing task in response to case event.");
803 private void logFutureException(ListenableFuture<?> future, String errorLogMessage, String errorUserMessage) {
804 future.addListener(() -> {
807 }
catch (InterruptedException | ExecutionException ex) {
808 logger.log(Level.SEVERE, errorLogMessage, ex);
811 }, MoreExecutors.directExecutor());
synchronized ReadOnlyDoubleProperty taskProgressProperty()
synchronized void pushZoomInTime()
void unPinEvent(DetailViewEvent event)
synchronized void pushDescrLOD(TimelineEvent.DescriptionLevel newLOD)
boolean hasFilterState(RootFilterState filterSet)
FilteredEventsModel getEventsModel()
static final ReadOnlyObjectWrapper< TimeZone > timeZone
synchronized ViewMode getViewMode()
void logFutureException(ListenableFuture<?> future, String errorLogMessage, String errorUserMessage)
ZoomState withFilterState(RootFilterState filter)
boolean hasTypeZoomLevel(TimelineEventType.TypeLevel typeZoom)
static TimeZone getTimeZone()
synchronized void setViewMode(ViewMode viewMode)
RootFilterState getFilterState()
synchronized ReadOnlyObjectProperty< Interval > selectedTimeRangeProperty()
boolean hasDescrLOD(TimelineEvent.DescriptionLevel newLOD)
synchronized boolean pushTimeUnit(TimeUnits timeUnit)
synchronized boolean pushTimeRange(Interval timeRange)
synchronized ReadOnlyStringProperty taskTitleProperty()
synchronized void unRegisterForEvents(Object listener)
ZoomState withTimeAndType(Interval timeRange, TimelineEventType.TypeLevel zoomLevel)
static ReadOnlyObjectProperty< TimeZone > timeZoneProperty()
synchronized Interval getSelectedTimeRange()
synchronized ReadOnlyStringProperty taskMessageProperty()
void applyDefaultFilters()
static Interval getIntervalAroundMiddle(Interval interval, ReadablePeriod period)
static ZoneId getTimeZoneID()
synchronized void registerForEvents(Object listener)
boolean hasTimeRange(Interval timeRange)
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)
synchronized void pushEventTypeZoom(TimelineEventType.TypeLevel typeZoomeLevel)
ZoomState withDescrLOD(TimelineEvent.DescriptionLevel descrLOD)
ReadOnlyStringProperty statusMessageProperty()
synchronized TimeLineTopComponent getTopComponent()
synchronized void retreat()
static DateTimeZone getJodaTimeZone()
synchronized void advance()
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
ZoomState withTypeZoomLevel(TimelineEventType.TypeLevel zoomLevel)
synchronized ReadOnlyBooleanProperty canAdvanceProperty()
ZoomState withTimeRange(Interval timeRange)
synchronized void pushZoomOutTime()
static DateTimeFormatter getZonedFormatter()
void pinEvent(DetailViewEvent event)
ObservableSet< DetailViewEvent > getPinnedEvents()
synchronized ReadOnlyListProperty< Task<?> > getTasks()
synchronized ReadOnlyBooleanProperty canRetreatProperty()
synchronized void advance(ZoomState newState)
void selectTimeAndType(Interval interval, TimelineEventType type)
static void error(String message)
void setStatusMessage(String string)