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;
94 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<
EventsModelParams> 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;
283 filteredEvents =
new EventsModel(autoCase, currentParams.getReadOnlyProperty());
290 historyManager.currentState().addListener((observable, oldState, newState) -> {
293 currentParams.set(historyManagerState);
298 InitialZoomState =
new EventsModelParams(filteredEvents.getSpanningInterval(),
299 TimelineEventType.HierarchyLevel.CATEGORY,
300 filteredEvents.eventFilterProperty().get(),
301 TimelineLevelOfDetail.LOW);
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.getDefaultEventFilterState());
329 Interval boundingEventsInterval = filteredEvents.getSpanningInterval(getJodaTimeZone());
330 advance(filteredEvents.modelParamsProperty().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);
383 void shutDownTimeLineListeners() {
390 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
391 public
void shutDownTimeLineGui() {
392 if (topComponent != null) {
393 topComponent.close();
409 void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
410 Platform.runLater(() -> {
412 if (promptDialogManager.bringCurrentDialogToFront()) {
417 IngestManager.getInstance().isIngestRunning()
418 && promptDialogManager.confirmDuringIngest() ==
false) {
422 if (file == null && artifact == null) {
423 SwingUtilities.invokeLater(TimeLineController.this::showWindow);
424 this.showFullRange();
428 ShowInTimelineDialog showInTimelineDilaog = (file == null)
429 ?
new ShowInTimelineDialog(
this, artifact)
430 :
new ShowInTimelineDialog(
this, file);
431 Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
432 dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
433 SwingUtilities.invokeLater(this::showWindow);
435 showInListView(viewInTimelineRequestedEvent);
436 }
catch (TskCoreException ex) {
437 logger.log(Level.SEVERE,
"Error showing requested events in listview: " + viewInTimelineRequestedEvent, ex);
438 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
443 }
catch (TskCoreException tskCoreException) {
444 logger.log(Level.SEVERE,
"Error showing Timeline ", tskCoreException);
445 new Alert(Alert.AlertType.ERROR,
"There was an error opening Timeline.").showAndWait();
458 synchronized public void pushPeriod(ReadablePeriod period)
throws TskCoreException {
459 synchronized (filteredEvents) {
465 final Interval timeRange = filteredEvents.getTimeRange();
466 long toDurationMillis = timeRange.toDurationMillis() / 4;
467 DateTime start = timeRange.getStart().minus(toDurationMillis);
468 DateTime end = timeRange.getEnd().plus(toDurationMillis);
469 pushTimeRange(
new Interval(start, end));
473 final Interval timeRange = filteredEvents.getTimeRange();
474 long toDurationMillis = timeRange.toDurationMillis() / 4;
475 DateTime start = timeRange.getStart().plus(toDurationMillis);
476 DateTime end = timeRange.getEnd().minus(toDurationMillis);
477 pushTimeRange(
new Interval(start, end));
485 synchronized private
void showWindow() {
486 if (topComponent == null) {
489 if (topComponent.isOpened() ==
false) {
492 topComponent.toFront();
497 topComponent.requestActive();
506 if (currentZoom == null) {
507 advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
522 synchronized public boolean pushTimeRange(Interval timeRange)
throws TskCoreException {
524 Interval clampedTimeRange;
525 if (timeRange == null) {
526 clampedTimeRange = this.filteredEvents.getSpanningInterval();
528 Interval spanningInterval = this.filteredEvents.getSpanningInterval();
529 if (spanningInterval.overlaps(timeRange)) {
530 clampedTimeRange = spanningInterval.overlap(timeRange);
532 clampedTimeRange = spanningInterval;
537 if (currentZoom == null) {
538 advance(InitialZoomState.withTimeRange(clampedTimeRange));
540 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
560 return showFullRange();
568 if (currentZoom == null) {
569 advance(InitialZoomState.withDescrLOD(newLOD));
570 }
else if (currentZoom.
hasDescrLOD(newLOD) ==
false) {
575 @SuppressWarnings(
"AssignmentToMethodParameter")
576 synchronized public
void pushTimeAndType(Interval timeRange, TimelineEventType.HierarchyLevel typeZoom) throws TskCoreException {
577 Interval overlappingTimeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
579 if (currentZoom == null) {
580 advance(InitialZoomState.withTimeAndType(overlappingTimeRange, typeZoom));
583 }
else if (currentZoom.
hasTimeRange(overlappingTimeRange) ==
false) {
592 if (currentZoom == null) {
593 advance(InitialZoomState.withFilterState(filter));
600 historyManager.advance();
604 historyManager.retreat();
608 historyManager.advance(newState);
617 final synchronized public void selectEventIDs(Collection<Long> eventIDs)
throws TskCoreException {
618 selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
619 selectedEventIDs.setAll(eventIDs);
622 public void selectTimeAndType(Interval interval, TimelineEventType type)
throws TskCoreException {
623 final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
627 protected Collection< Long> call()
throws Exception {
629 return filteredEvents.getEventIDs(timeRange,
new SqlFilterState<>(
new EventTypeFilter(type),
true));
634 protected void succeeded() {
638 selectedTimeRange.set(timeRange);
639 selectedEventIDs.setAll(
get());
642 }
catch (InterruptedException | ExecutionException ex) {
643 logger.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
648 monitorTask(selectTimeAndTypeTask);
660 Platform.runLater(() -> {
663 task.stateProperty().addListener((Observable observable) -> {
664 switch (task.getState()) {
673 if (tasks.isEmpty() ==
false) {
674 taskProgress.bind(tasks.get(0).progressProperty());
675 taskMessage.bind(tasks.get(0).messageProperty());
676 taskTitle.bind(tasks.get(0).titleProperty());
682 taskProgress.bind(task.progressProperty());
683 taskMessage.bind(task.messageProperty());
684 taskTitle.bind(task.titleProperty());
685 switch (task.getState()) {
688 executor.submit(task);
697 if (tasks.isEmpty() ==
false) {
698 taskProgress.bind(tasks.get(0).progressProperty());
699 taskMessage.bind(tasks.get(0).messageProperty());
700 taskTitle.bind(tasks.get(0).titleProperty());
715 eventbus.register(listener);
724 eventbus.unregister(listener);
732 void handleIngestModuleEvent(PropertyChangeEvent evt) {
740 }
catch (NoCurrentCaseException notUsed) {
746 if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
750 switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
751 case CONTENT_CHANGED:
755 ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
756 if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_HASHSET_HIT.getTypeID()) {
757 logFutureException(executor.submit(() -> filteredEvents.updateEventsForHashSetHits(eventData.getArtifacts())),
758 "Error executing task in response to DATA_ADDED event.",
759 "Error executing response to new data.");
775 void handleCaseEvent(PropertyChangeEvent evt) {
776 ListenableFuture<?> future = Futures.immediateFuture(null);
777 switch (Case.Events.valueOf(evt.getPropertyName())) {
778 case BLACKBOARD_ARTIFACT_TAG_ADDED:
779 future = executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
781 case BLACKBOARD_ARTIFACT_TAG_DELETED:
782 future = executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
784 case CONTENT_TAG_ADDED:
785 future = executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
787 case CONTENT_TAG_DELETED:
788 future = executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
790 case DATA_SOURCE_ADDED:
791 future = executor.submit(() -> {
792 filteredEvents.handleDataSourceAdded();
796 case TIMELINE_EVENT_ADDED:
797 future = executor.submit(() -> {
798 filteredEvents.invalidateCaches(singleton(((TimelineEventAddedEvent) evt).getAddedEventID()));
803 logFutureException(future,
804 "Error executing task in response to " + evt.getPropertyName() +
" event.",
805 "Error executing task in response to case event.");
808 private void logFutureException(ListenableFuture<?> future, String errorLogMessage, String errorUserMessage) {
809 future.addListener(() -> {
812 }
catch (InterruptedException | ExecutionException ex) {
813 logger.log(Level.SEVERE, errorLogMessage, ex);
815 }, 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)