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;
578 Platform.runLater(() -> promptForRebuild(file, artifact));
591 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
592 private
void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) {
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) {
701 if (topComponent.isOpened() ==
false) {
704 topComponent.toFront();
709 topComponent.requestActive();
713 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
714 if (currentZoom == null) {
715 advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
732 Interval clampedTimeRange;
733 if (timeRange == null) {
734 clampedTimeRange = this.filteredEvents.getSpanningInterval();
736 Interval spanningInterval = this.filteredEvents.getSpanningInterval();
737 if (spanningInterval.overlaps(timeRange)) {
738 clampedTimeRange = spanningInterval.overlap(timeRange);
740 clampedTimeRange = spanningInterval;
744 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
745 if (currentZoom == null) {
746 advance(InitialZoomState.withTimeRange(clampedTimeRange));
748 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
766 return showFullRange();
773 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
774 if (currentZoom == null) {
775 advance(InitialZoomState.withDescrLOD(newLOD));
776 }
else if (currentZoom.
hasDescrLOD(newLOD) ==
false) {
781 @SuppressWarnings(
"AssignmentToMethodParameter")
783 timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
784 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
785 if (currentZoom == null) {
786 advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
789 }
else if (currentZoom.
hasTimeRange(timeRange) ==
false) {
797 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
798 if (currentZoom == null) {
799 advance(InitialZoomState.withFilter(filter.
copyOf()));
800 }
else if (currentZoom.
hasFilter(filter) ==
false) {
806 historyManager.advance();
810 historyManager.retreat();
814 historyManager.advance(newState);
824 selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
825 selectedEventIDs.setAll(eventIDs);
829 final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
833 protected Collection< Long> call()
throws Exception {
835 return filteredEvents.getEventIDs(timeRange,
new TypeFilter(type));
840 protected void succeeded() {
844 selectedTimeRange.set(timeRange);
845 selectedEventIDs.setAll(
get());
848 }
catch (InterruptedException | ExecutionException ex) {
849 LOGGER.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
854 monitorTask(selectTimeAndTypeTask);
866 Platform.runLater(() -> {
869 task.stateProperty().addListener((Observable observable) -> {
870 switch (task.getState()) {
879 if (tasks.isEmpty() ==
false) {
880 taskProgress.bind(tasks.get(0).progressProperty());
881 taskMessage.bind(tasks.get(0).messageProperty());
882 taskTitle.bind(tasks.get(0).titleProperty());
888 taskProgress.bind(task.progressProperty());
889 taskMessage.bind(task.messageProperty());
890 taskTitle.bind(task.titleProperty());
891 switch (task.getState()) {
893 executor.submit(task);
902 if (tasks.isEmpty() ==
false) {
903 taskProgress.bind(tasks.get(0).progressProperty());
904 taskMessage.bind(tasks.get(0).messageProperty());
905 taskTitle.bind(tasks.get(0).titleProperty());
920 eventbus.register(o);
929 eventbus.unregister(0);
953 }
catch (IllegalStateException notUsed) {
959 case CONTENT_CHANGED:
962 Platform.runLater(() -> setEventsDBStale(
true));
983 case DATA_SOURCE_ANALYSIS_COMPLETED:
985 Platform.runLater(() -> setEventsDBStale(
true));
986 filteredEvents.postAutopsyEventLocally((
AutopsyEvent) evt);
988 case DATA_SOURCE_ANALYSIS_STARTED:
1005 switch (
Case.
Events.valueOf(evt.getPropertyName())) {
1006 case BLACKBOARD_ARTIFACT_TAG_ADDED:
1009 case BLACKBOARD_ARTIFACT_TAG_DELETED:
1012 case CONTENT_TAG_ADDED:
1015 case CONTENT_TAG_DELETED:
1018 case DATA_SOURCE_ADDED:
1020 Platform.runLater(() -> setEventsDBStale(
true));
1021 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)