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);
 
  501         rebuildRepoHelper(eventsRepository::rebuildRepository, 
true, file, 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();
 
  576             listeningToAutopsy = 
true;
 
  578         Platform.runLater(() -> promptForRebuild(file, artifact));
 
  591     @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
 
  594         if (promptDialogManager.bringCurrentDialogToFront()) {
 
  599         if (eventsRepository.countAllEvents() == 0) {
 
  600             rebuildRepo(file, artifact);
 
  605         List<String> rebuildReasons = getRebuildReasons();
 
  606         if (
false == rebuildReasons.isEmpty()) {
 
  607             if (promptDialogManager.confirmRebuild(rebuildReasons)) {
 
  608                 rebuildRepo(file, artifact);
 
  620         rebuildTagsTable(file, artifact);
 
  631     @NbBundle.Messages({
"TimeLineController.errorTitle=Timeline error.",
 
  632         "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.",
 
  633         "TimeLineController.rebuildReasons.outOfDateError=Could not determine if the timeline data is out of date.",
 
  634         "TimeLineController.rebuildReasons.outOfDate=The event data is out of date:  Not all events will be visible.",
 
  635         "TimeLineController.rebuildReasons.ingestWasRunning=The Timeline events database was previously populated while ingest was running:  Some events may be missing, incomplete, or inaccurate.",
 
  636         "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."})
 
  638         ArrayList<String> rebuildReasons = 
new ArrayList<>();
 
  642             if (perCaseTimelineProperties.wasIngestRunning()) {
 
  643                 rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning());
 
  646         } 
catch (IOException ex) {
 
  647             LOGGER.log(Level.SEVERE, 
"Error determing the state of the timeline db. We will assume the it is out of date.", ex); 
 
  649                     Bundle.TimeLineController_outOfDate_errorMessage());
 
  650             rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError());
 
  653         if (isEventsDBStale()) {
 
  654             rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDate());
 
  657         if (eventsRepository.hasNewColumns() == 
false) {
 
  658             rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema());
 
  660         return rebuildReasons;
 
  671         synchronized (filteredEvents) {
 
  677         final Interval timeRange = filteredEvents.timeRangeProperty().get();
 
  678         long toDurationMillis = timeRange.toDurationMillis() / 4;
 
  679         DateTime start = timeRange.getStart().minus(toDurationMillis);
 
  680         DateTime end = timeRange.getEnd().plus(toDurationMillis);
 
  681         pushTimeRange(
new Interval(start, end));
 
  685         final Interval timeRange = filteredEvents.timeRangeProperty().get();
 
  686         long toDurationMillis = timeRange.toDurationMillis() / 4;
 
  687         DateTime start = timeRange.getStart().plus(toDurationMillis);
 
  688         DateTime end = timeRange.getEnd().minus(toDurationMillis);
 
  689         pushTimeRange(
new Interval(start, end));
 
  697     synchronized private 
void showWindow() {
 
  698         if (topComponent == null) {
 
  702         topComponent.toFront();
 
  707         topComponent.requestActive();
 
  711         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  712         if (currentZoom == null) {
 
  713             advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
 
  730         Interval clampedTimeRange;
 
  731         if (timeRange == null) {
 
  732             clampedTimeRange = this.filteredEvents.getSpanningInterval();
 
  734             Interval spanningInterval = this.filteredEvents.getSpanningInterval();
 
  735             if (spanningInterval.overlaps(timeRange)) {
 
  736                 clampedTimeRange = spanningInterval.overlap(timeRange);
 
  738                 clampedTimeRange = spanningInterval;
 
  742         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  743         if (currentZoom == null) {
 
  744             advance(InitialZoomState.withTimeRange(clampedTimeRange));
 
  746         } 
else if (currentZoom.
hasTimeRange(clampedTimeRange) == 
false) {
 
  764             return showFullRange();
 
  771         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  772         if (currentZoom == null) {
 
  773             advance(InitialZoomState.withDescrLOD(newLOD));
 
  774         } 
else if (currentZoom.
hasDescrLOD(newLOD) == 
false) {
 
  779     @SuppressWarnings(
"AssignmentToMethodParameter") 
 
  781         timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
 
  782         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  783         if (currentZoom == null) {
 
  784             advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
 
  787         } 
else if (currentZoom.
hasTimeRange(timeRange) == 
false) {
 
  795         ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
 
  796         if (currentZoom == null) {
 
  797             advance(InitialZoomState.withFilter(filter.
copyOf()));
 
  798         } 
else if (currentZoom.
hasFilter(filter) == 
false) {
 
  804         historyManager.advance();
 
  808         historyManager.retreat();
 
  812         historyManager.advance(newState);
 
  822         selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
 
  823         selectedEventIDs.setAll(eventIDs);
 
  827         final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
 
  831             protected Collection< Long> call() 
throws Exception {
 
  833                     return filteredEvents.getEventIDs(timeRange, 
new TypeFilter(type));
 
  838             protected void succeeded() {
 
  842                         selectedTimeRange.set(timeRange);
 
  843                         selectedEventIDs.setAll(
get());
 
  846                 } 
catch (InterruptedException | ExecutionException ex) {
 
  847                     LOGGER.log(Level.SEVERE, getTitle() + 
" Unexpected error", ex); 
 
  852         monitorTask(selectTimeAndTypeTask);
 
  864             Platform.runLater(() -> {
 
  867                 task.stateProperty().addListener((Observable observable) -> {
 
  868                     switch (task.getState()) {
 
  877                             if (tasks.isEmpty() == 
false) {
 
  878                                 taskProgress.bind(tasks.get(0).progressProperty());
 
  879                                 taskMessage.bind(tasks.get(0).messageProperty());
 
  880                                 taskTitle.bind(tasks.get(0).titleProperty());
 
  886                 taskProgress.bind(task.progressProperty());
 
  887                 taskMessage.bind(task.messageProperty());
 
  888                 taskTitle.bind(task.titleProperty());
 
  889                 switch (task.getState()) {
 
  891                         executor.submit(task);
 
  900                         if (tasks.isEmpty() == 
false) {
 
  901                             taskProgress.bind(tasks.get(0).progressProperty());
 
  902                             taskMessage.bind(tasks.get(0).messageProperty());
 
  903                             taskTitle.bind(tasks.get(0).titleProperty());
 
  918         eventbus.register(o);
 
  927         eventbus.unregister(0);
 
  951             } 
catch (IllegalStateException notUsed) {
 
  957                 case CONTENT_CHANGED:
 
  960                     Platform.runLater(() -> setEventsDBStale(
true));
 
  981                 case DATA_SOURCE_ANALYSIS_COMPLETED:
 
  983                     Platform.runLater(() -> setEventsDBStale(
true));
 
  984                     filteredEvents.postAutopsyEventLocally((
AutopsyEvent) evt);
 
  986                 case DATA_SOURCE_ANALYSIS_STARTED:
 
 1003             switch (
Case.
Events.valueOf(evt.getPropertyName())) {
 
 1004                 case BLACKBOARD_ARTIFACT_TAG_ADDED:
 
 1007                 case BLACKBOARD_ARTIFACT_TAG_DELETED:
 
 1010                 case CONTENT_TAG_ADDED:
 
 1013                 case CONTENT_TAG_DELETED:
 
 1016                 case DATA_SOURCE_ADDED:
 
 1018                     Platform.runLater(() -> setEventsDBStale(
true));
 
 1019                     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)