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)