19 package org.sleuthkit.autopsy.timeline;
 
   21 import com.google.common.eventbus.EventBus;
 
   22 import java.beans.PropertyChangeEvent;
 
   23 import java.beans.PropertyChangeListener;
 
   24 import java.io.IOException;
 
   25 import java.time.ZoneId;
 
   26 import java.util.ArrayList;
 
   27 import java.util.Collection;
 
   28 import java.util.Collections;
 
   29 import java.util.List;
 
   30 import java.util.Optional;
 
   31 import java.util.TimeZone;
 
   32 import java.util.concurrent.ExecutionException;
 
   33 import java.util.concurrent.ExecutorService;
 
   34 import java.util.concurrent.Executors;
 
   35 import java.util.function.Consumer;
 
   36 import java.util.function.Function;
 
   37 import java.util.logging.Level;
 
   38 import javafx.application.Platform;
 
   39 import javafx.beans.Observable;
 
   40 import javafx.beans.property.ReadOnlyBooleanProperty;
 
   41 import javafx.beans.property.ReadOnlyBooleanWrapper;
 
   42 import javafx.beans.property.ReadOnlyDoubleProperty;
 
   43 import javafx.beans.property.ReadOnlyDoubleWrapper;
 
   44 import javafx.beans.property.ReadOnlyListProperty;
 
   45 import javafx.beans.property.ReadOnlyListWrapper;
 
   46 import javafx.beans.property.ReadOnlyObjectProperty;
 
   47 import javafx.beans.property.ReadOnlyObjectWrapper;
 
   48 import javafx.beans.property.ReadOnlyStringProperty;
 
   49 import javafx.beans.property.ReadOnlyStringWrapper;
 
   50 import javafx.collections.FXCollections;
 
   51 import javafx.collections.ObservableList;
 
   52 import javafx.collections.ObservableSet;
 
   53 import javafx.concurrent.Task;
 
   54 import javafx.concurrent.Worker;
 
   55 import static javafx.concurrent.Worker.State.FAILED;
 
   56 import static javafx.concurrent.Worker.State.SUCCEEDED;
 
   57 import javax.annotation.concurrent.GuardedBy;
 
   58 import javax.annotation.concurrent.Immutable;
 
   59 import javax.swing.SwingUtilities;
 
   60 import org.joda.time.DateTime;
 
   61 import org.joda.time.DateTimeZone;
 
   62 import org.joda.time.Interval;
 
   63 import org.joda.time.ReadablePeriod;
 
   64 import org.joda.time.format.DateTimeFormat;
 
   65 import org.joda.time.format.DateTimeFormatter;
 
   66 import org.openide.util.NbBundle;
 
  114 @NbBundle.Messages({
"Timeline.dialogs.title= Timeline",
 
  115     "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
 
  120     private static final ReadOnlyObjectWrapper<TimeZone> timeZone = 
new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
 
  123         return timeZone.get().toZoneId();
 
  127         return DateTimeFormat.forPattern(
"YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone()); 
 
  131         return DateTimeZone.forTimeZone(getTimeZone().
get());
 
  135         return timeZone.getReadOnlyProperty();
 
  138     private final ExecutorService executor = Executors.newSingleThreadExecutor();
 
  140     private final ReadOnlyListWrapper<Task<?>> tasks = 
new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
 
  142     private final ReadOnlyDoubleWrapper taskProgress = 
new ReadOnlyDoubleWrapper(-1);
 
  144     private final ReadOnlyStringWrapper taskMessage = 
new ReadOnlyStringWrapper();
 
  146     private final ReadOnlyStringWrapper taskTitle = 
new ReadOnlyStringWrapper();
 
  148     private final ReadOnlyStringWrapper statusMessage = 
new ReadOnlyStringWrapper();
 
  149     private EventBus eventbus = 
new EventBus(
"TimeLineController_EventBus");
 
  158         return statusMessage.getReadOnlyProperty();
 
  162         statusMessage.set(
string);
 
  168     private final ObservableList<
DescriptionFilter> quickHideFilters = FXCollections.observableArrayList();
 
  171         return quickHideFilters;
 
  181     synchronized public ReadOnlyListProperty<Task<?>> 
getTasks() {
 
  182         return tasks.getReadOnlyProperty();
 
  186         return taskProgress.getReadOnlyProperty();
 
  190         return taskMessage.getReadOnlyProperty();
 
  194         return taskTitle.getReadOnlyProperty();
 
  202     private 
boolean listeningToAutopsy = false;
 
  209     private final ReadOnlyObjectWrapper<
ViewMode> viewMode = new ReadOnlyObjectWrapper<>(
ViewMode.COUNTS);
 
  211     @GuardedBy("filteredEvents")
 
  223     private final ReadOnlyObjectWrapper<
ZoomParams> currentParams = new ReadOnlyObjectWrapper<>();
 
  227     private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
 
  230     private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
 
  232     private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
 
  241     synchronized public ObservableList<Long> getSelectedEventIDs() {
 
  242         return selectedEventIDs;
 
  251         return selectedTimeRange.getReadOnlyProperty();
 
  260         return selectedTimeRange.get();
 
  264         return eventsDBStale.getReadOnlyProperty();
 
  273         return eventsDBStale.get();
 
  282         "TimeLineController.setEventsDBStale.errMsgStale=Failed to mark the timeline db as stale. Some results may be out of date or missing.",
 
  283         "TimeLineController.setEventsDBStale.errMsgNotStale=Failed to mark the timeline db as not stale. Some results may be out of date or missing."})
 
  284     @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
 
  286         eventsDBStale.set(stale);
 
  289             perCaseTimelineProperties.setDbStale(stale);
 
  290         } 
catch (IOException ex) {
 
  292                     stale ? Bundle.TimeLineController_setEventsDBStale_errMsgStale()
 
  293                             : Bundle.TimeLineController_setEventsDBStale_errMsgNotStale());
 
  294             LOGGER.log(Level.SEVERE, 
"Error marking the timeline db as stale.", ex); 
 
  299         return historyManager.getCanAdvance();
 
  303         return historyManager.getCanRetreat();
 
  307         return viewMode.getReadOnlyProperty();
 
  316         if (this.viewMode.get() != viewMode) {
 
  317             this.viewMode.set(viewMode);
 
  327         return viewMode.get();
 
  331         this.autoCase = autoCase;
 
  332         this.perCaseTimelineProperties = 
new PerCaseTimelineProperties(autoCase);
 
  333         eventsDBStale.set(perCaseTimelineProperties.isDBStale());
 
  334         eventsRepository = 
new EventsRepository(autoCase, currentParams.getReadOnlyProperty());
 
  342         historyManager.currentState().addListener((Observable observable) -> {
 
  343             ZoomParams historyManagerParams = historyManager.getCurrentState();
 
  345             currentParams.set(historyManagerParams);
 
  349         InitialZoomState = 
new ZoomParams(filteredEvents.getSpanningInterval(),
 
  351                 filteredEvents.filterProperty().get(),
 
  353         historyManager.advance(InitialZoomState);
 
  356         viewMode.addListener(observable -> selectEventIDs(Collections.emptySet()));
 
  363         return filteredEvents;
 
  367         pushFilters(filteredEvents.getDefaultFilter());
 
  371         Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval();
 
  372         advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval));
 
  375     private final ObservableSet<TimeLineEvent> pinnedEvents = FXCollections.observableSet();
 
  376     private final ObservableSet<TimeLineEvent> pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents);
 
  379         pinnedEvents.add(event);
 
  383         pinnedEvents.removeIf(event::equals);
 
  387         return pinnedEventsUnmodifiable;
 
  412         "TimeLineController.setIngestRunning.errMsgRunning=Failed to mark the timeline db as populated while ingest was running. Some results may be out of date or missing.",
 
  413         "TimeLinecontroller.setIngestRunning.errMsgNotRunning=Failed to mark the timeline db as populated while ingest was not running. Some results may be out of date or missing."})
 
  418         if (promptDialogManager.bringCurrentDialogToFront()) {
 
  423         if (ingestRunning && promptDialogManager.confirmDuringIngest() == 
false) {
 
  429         rebuildRepositoryTask = repoBuilder.apply(
new Consumer<Worker.State>() {
 
  431             public void accept(Worker.State newSate) {
 
  440                             perCaseTimelineProperties.setIngestRunning(ingestRunning);
 
  441                         } 
catch (IOException ex) {
 
  443                                     ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
 
  444                                             : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
 
  445                             LOGGER.log(Level.SEVERE, 
"Error marking the ingest state while the timeline db was populated.", ex); 
 
  447                         if (markDBNotStale) {
 
  448                             setEventsDBStale(
false);
 
  449                             filteredEvents.postDBUpdated();
 
  451                         if (file == null && artifact == null) {
 
  456                             ShowInTimelineDialog showInTimelineDilaog =
 
  460                             Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
 
  461                             dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
 
  463                                 showInListView(viewInTimelineRequestedEvent); 
 
  469                         setEventsDBStale(
true);
 
  479         promptDialogManager.showDBPopulationProgressDialog(rebuildRepositoryTask);
 
  487     public 
void rebuildRepo() {
 
  488         rebuildRepo(null, null);
 
  500     private 
void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) {
 
  501         rebuildRepoHelper(eventsRepository::rebuildRepository, 
true, file, artifact);
 
  513     private 
void rebuildTagsTable(AbstractFile file, BlackboardArtifact artifact) {
 
  514         rebuildRepoHelper(eventsRepository::rebuildTags, 
false, file, artifact);
 
  521         synchronized (filteredEvents) {
 
  522             return pushTimeRange(filteredEvents.getSpanningInterval());
 
  535         synchronized (filteredEvents) {
 
  537             selectEventIDs(requestEvent.getEventIDs());
 
  538             if (pushTimeRange(requestEvent.getInterval()) == 
false) {
 
  539                 eventbus.post(requestEvent);
 
  549     public 
void shutDownTimeLine() {
 
  550         listeningToAutopsy = 
false;
 
  554         if (topComponent != null) {
 
  555             topComponent.close();
 
  570     void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
 
  576             listeningToAutopsy = 
true;
 
  579         Platform.runLater(() -> promptForRebuild(file, artifact));
 
  592     @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
 
  593     private 
void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) {
 
  595         if (promptDialogManager.bringCurrentDialogToFront()) {
 
  600         if (eventsRepository.countAllEvents() == 0) {
 
  601             rebuildRepo(file, artifact);
 
  606         List<String> rebuildReasons = getRebuildReasons();
 
  607         if (
false == rebuildReasons.isEmpty()) {
 
  608             if (promptDialogManager.confirmRebuild(rebuildReasons)) {
 
  609                 rebuildRepo(file, artifact);
 
  621         rebuildTagsTable(file, artifact);
 
  632     @NbBundle.Messages({
"TimeLineController.errorTitle=Timeline error.",
 
  633         "TimeLineController.outOfDate.errorMessage=Error determing if the timeline is out of date.  We will assume it should be updated.  See the logs for more details.",
 
  634         "TimeLineController.rebuildReasons.outOfDateError=Could not determine if the timeline data is out of date.",
 
  635         "TimeLineController.rebuildReasons.outOfDate=The event data is out of date:  Not all events will be visible.",
 
  636         "TimeLineController.rebuildReasons.ingestWasRunning=The Timeline events database was previously populated while ingest was running:  Some events may be missing, incomplete, or inaccurate.",
 
  637         "TimeLineController.rebuildReasons.incompleteOldSchema=The Timeline events database was previously populated without incomplete information:  Some features may be unavailable or non-functional unless you update the events database."})
 
  639         ArrayList<String> rebuildReasons = 
new ArrayList<>();
 
  643             if (perCaseTimelineProperties.wasIngestRunning()) {
 
  644                 rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning());
 
  647         } 
catch (IOException ex) {
 
  648             LOGGER.log(Level.SEVERE, 
"Error determing the state of the timeline db. We will assume the it is out of date.", ex); 
 
  650                     Bundle.TimeLineController_outOfDate_errorMessage());
 
  651             rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError());
 
  654         if (isEventsDBStale()) {
 
  655             rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDate());
 
  658         if (eventsRepository.hasNewColumns() == 
false) {
 
  659             rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema());
 
  661         return rebuildReasons;
 
  672         synchronized (filteredEvents) {
 
  678         final Interval timeRange = filteredEvents.timeRangeProperty().get();
 
  679         long toDurationMillis = timeRange.toDurationMillis() / 4;
 
  680         DateTime start = timeRange.getStart().minus(toDurationMillis);
 
  681         DateTime end = timeRange.getEnd().plus(toDurationMillis);
 
  682         pushTimeRange(
new Interval(start, end));
 
  686         final Interval timeRange = filteredEvents.timeRangeProperty().get();
 
  687         long toDurationMillis = timeRange.toDurationMillis() / 4;
 
  688         DateTime start = timeRange.getStart().plus(toDurationMillis);
 
  689         DateTime end = timeRange.getEnd().minus(toDurationMillis);
 
  690         pushTimeRange(
new Interval(start, end));
 
  698     synchronized private 
void showWindow() {
 
  699         if (topComponent == null) {
 
  703         topComponent.toFront();
 
  708         topComponent.requestActive();
 
  712         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  713         if (currentZoom == null) {
 
  714             advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
 
  731         Interval clampedTimeRange;
 
  732         if (timeRange == null) {
 
  733             clampedTimeRange = this.filteredEvents.getSpanningInterval();
 
  735             Interval spanningInterval = this.filteredEvents.getSpanningInterval();
 
  736             if (spanningInterval.overlaps(timeRange)) {
 
  737                 clampedTimeRange = spanningInterval.overlap(timeRange);
 
  739                 clampedTimeRange = spanningInterval;
 
  743         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  744         if (currentZoom == null) {
 
  745             advance(InitialZoomState.withTimeRange(clampedTimeRange));
 
  747         } 
else if (currentZoom.
hasTimeRange(clampedTimeRange) == 
false) {
 
  765             return showFullRange();
 
  772         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  773         if (currentZoom == null) {
 
  774             advance(InitialZoomState.withDescrLOD(newLOD));
 
  775         } 
else if (currentZoom.
hasDescrLOD(newLOD) == 
false) {
 
  780     @SuppressWarnings(
"AssignmentToMethodParameter") 
 
  782         timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
 
  783         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  784         if (currentZoom == null) {
 
  785             advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
 
  788         } 
else if (currentZoom.
hasTimeRange(timeRange) == 
false) {
 
  796         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  797         if (currentZoom == null) {
 
  798             advance(InitialZoomState.withFilter(filter.
copyOf()));
 
  799         } 
else if (currentZoom.
hasFilter(filter) == 
false) {
 
  805         historyManager.advance();
 
  809         historyManager.retreat();
 
  813         historyManager.advance(newState);
 
  823         selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
 
  824         selectedEventIDs.setAll(eventIDs);
 
  828         final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
 
  832             protected Collection< Long> call() 
throws Exception {
 
  834                     return filteredEvents.getEventIDs(timeRange, 
new TypeFilter(type));
 
  839             protected void succeeded() {
 
  843                         selectedTimeRange.set(timeRange);
 
  844                         selectedEventIDs.setAll(
get());
 
  847                 } 
catch (InterruptedException | ExecutionException ex) {
 
  848                     LOGGER.log(Level.SEVERE, getTitle() + 
" Unexpected error", ex); 
 
  853         monitorTask(selectTimeAndTypeTask);
 
  865             Platform.runLater(() -> {
 
  868                 task.stateProperty().addListener((Observable observable) -> {
 
  869                     switch (task.getState()) {
 
  878                             if (tasks.isEmpty() == 
false) {
 
  879                                 taskProgress.bind(tasks.get(0).progressProperty());
 
  880                                 taskMessage.bind(tasks.get(0).messageProperty());
 
  881                                 taskTitle.bind(tasks.get(0).titleProperty());
 
  887                 taskProgress.bind(task.progressProperty());
 
  888                 taskMessage.bind(task.messageProperty());
 
  889                 taskTitle.bind(task.titleProperty());
 
  890                 switch (task.getState()) {
 
  892                         executor.submit(task);
 
  901                         if (tasks.isEmpty() == 
false) {
 
  902                             taskProgress.bind(tasks.get(0).progressProperty());
 
  903                             taskMessage.bind(tasks.get(0).messageProperty());
 
  904                             taskTitle.bind(tasks.get(0).titleProperty());
 
  919         eventbus.register(o);
 
  928         eventbus.unregister(0);
 
  952             } 
catch (IllegalStateException notUsed) {
 
  958                 case CONTENT_CHANGED:
 
  961                     Platform.runLater(() -> setEventsDBStale(
true));
 
  982                 case DATA_SOURCE_ANALYSIS_COMPLETED:
 
  984                     Platform.runLater(() -> setEventsDBStale(
true));
 
  985                     filteredEvents.postAutopsyEventLocally((
AutopsyEvent) evt);
 
  987                 case DATA_SOURCE_ANALYSIS_STARTED:
 
 1004             switch (
Case.
Events.valueOf(evt.getPropertyName())) {
 
 1005                 case BLACKBOARD_ARTIFACT_TAG_ADDED:
 
 1008                 case BLACKBOARD_ARTIFACT_TAG_DELETED:
 
 1011                 case CONTENT_TAG_ADDED:
 
 1014                 case CONTENT_TAG_DELETED:
 
 1017                 case DATA_SOURCE_ADDED:
 
 1019                     Platform.runLater(() -> setEventsDBStale(
true));
 
 1020                     filteredEvents.postAutopsyEventLocally((
AutopsyEvent) evt);
 
synchronized ReadOnlyDoubleProperty taskProgressProperty()
synchronized void pushZoomInTime()
TimeLineController(Case autoCase)
void removeIngestModuleEventListener(final PropertyChangeListener listener)
void setEventsDBStale(final Boolean stale)
ObservableSet< TimeLineEvent > getPinnedEvents()
ZoomParams withDescrLOD(DescriptionLoD descrLOD)
static synchronized IngestManager getInstance()
boolean hasTimeRange(Interval timeRange)
FilteredEventsModel getEventsModel()
FilteredEventsModel getEventsModel()
ZoomParams withTypeZoomLevel(EventTypeZoomLevel zoomLevel)
ReadOnlyBooleanProperty eventsDBStaleProperty()
static final ReadOnlyObjectWrapper< TimeZone > timeZone
synchronized ViewMode getViewMode()
static ReadOnlyObjectProperty< TimeZone > getTimeZone()
void propertyChange(PropertyChangeEvent evt)
static void removePropertyChangeListener(PropertyChangeListener listener)
void rebuildRepoHelper(Function< Consumer< Worker.State >, CancellationProgressTask<?>> repoBuilder, Boolean markDBNotStale, AbstractFile file, BlackboardArtifact artifact)
synchronized void setViewMode(ViewMode viewMode)
boolean isIngestRunning()
synchronized void selectEventIDs(Collection< Long > eventIDs)
synchronized ReadOnlyObjectProperty< Interval > selectedTimeRangeProperty()
boolean hasFilter(RootFilter filterSet)
void pinEvent(TimeLineEvent event)
void selectTimeAndType(Interval interval, EventType type)
synchronized boolean pushTimeUnit(TimeUnits timeUnit)
synchronized boolean pushTimeRange(Interval timeRange)
void removeIngestJobEventListener(final PropertyChangeListener listener)
synchronized ReadOnlyStringProperty taskTitleProperty()
boolean hasTypeZoomLevel(EventTypeZoomLevel typeZoom)
synchronized Interval getSelectedTimeRange()
synchronized ReadOnlyStringProperty taskMessageProperty()
boolean hasDescrLOD(DescriptionLoD newLOD)
void applyDefaultFilters()
static Interval getIntervalAroundMiddle(Interval interval, ReadablePeriod period)
List< String > getRebuildReasons()
ZoomParams withTimeRange(Interval timeRange)
void propertyChange(PropertyChangeEvent evt)
synchronized void unRegisterForEvents(Object o)
void addIngestJobEventListener(final PropertyChangeListener listener)
static ZoneId getTimeZoneID()
static synchronized void setTimeZone(TimeZone timeZone)
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
synchronized void registerForEvents(Object o)
static void addPropertyChangeListener(PropertyChangeListener listener)
synchronized void advance(ZoomParams newState)
ReadOnlyStringProperty statusMessageProperty()
ZoomParams withTimeAndType(Interval timeRange, EventTypeZoomLevel zoomLevel)
synchronized void pushDescrLOD(DescriptionLoD newLOD)
synchronized void retreat()
static DateTimeZone getJodaTimeZone()
synchronized void pushFilters(RootFilter filter)
synchronized void advance()
final PerCaseTimelineProperties perCaseTimelineProperties
static void error(String title, String message)
void addIngestModuleEventListener(final PropertyChangeListener listener)
TagsFilter getTagsFilter()
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
void propertyChange(PropertyChangeEvent evt)
synchronized ReadOnlyBooleanProperty canAdvanceProperty()
ZoomParams withFilter(RootFilter filter)
boolean isEventsDBStale()
synchronized void pushZoomOutTime()
static DateTimeFormatter getZonedFormatter()
synchronized ReadOnlyListProperty< Task<?> > getTasks()
void unPinEvent(TimeLineEvent event)
synchronized ReadOnlyBooleanProperty canRetreatProperty()
static boolean isCaseOpen()
synchronized void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel)
void setStatusMessage(String string)