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;
115 @NbBundle.Messages({
"Timeline.dialogs.title= Timeline",
116 "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
121 private static final ReadOnlyObjectWrapper<TimeZone> timeZone =
new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
124 return timeZone.get().toZoneId();
128 return DateTimeFormat.forPattern(
"YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone());
132 return DateTimeZone.forTimeZone(getTimeZone().
get());
136 return timeZone.getReadOnlyProperty();
139 private final ExecutorService executor = Executors.newSingleThreadExecutor();
141 private final ReadOnlyListWrapper<Task<?>> tasks =
new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
143 private final ReadOnlyDoubleWrapper taskProgress =
new ReadOnlyDoubleWrapper(-1);
145 private final ReadOnlyStringWrapper taskMessage =
new ReadOnlyStringWrapper();
147 private final ReadOnlyStringWrapper taskTitle =
new ReadOnlyStringWrapper();
149 private final ReadOnlyStringWrapper statusMessage =
new ReadOnlyStringWrapper();
150 private EventBus eventbus =
new EventBus(
"TimeLineController_EventBus");
159 return statusMessage.getReadOnlyProperty();
163 statusMessage.set(
string);
169 private final ObservableList<
DescriptionFilter> quickHideFilters = FXCollections.observableArrayList();
172 return quickHideFilters;
182 synchronized public ReadOnlyListProperty<Task<?>>
getTasks() {
183 return tasks.getReadOnlyProperty();
187 return taskProgress.getReadOnlyProperty();
191 return taskMessage.getReadOnlyProperty();
195 return taskTitle.getReadOnlyProperty();
203 private
boolean listeningToAutopsy = false;
210 private final ReadOnlyObjectWrapper<
ViewMode> viewMode = new ReadOnlyObjectWrapper<>(
ViewMode.COUNTS);
212 @GuardedBy("filteredEvents")
224 private final ReadOnlyObjectWrapper<
ZoomParams> currentParams = new ReadOnlyObjectWrapper<>();
228 private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
231 private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
233 private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
242 synchronized public ObservableList<Long> getSelectedEventIDs() {
243 return selectedEventIDs;
252 return selectedTimeRange.getReadOnlyProperty();
261 return selectedTimeRange.get();
265 return eventsDBStale.getReadOnlyProperty();
274 return eventsDBStale.get();
283 "TimeLineController.setEventsDBStale.errMsgStale=Failed to mark the timeline db as stale. Some results may be out of date or missing.",
284 "TimeLineController.setEventsDBStale.errMsgNotStale=Failed to mark the timeline db as not stale. Some results may be out of date or missing."})
285 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
287 eventsDBStale.set(stale);
290 perCaseTimelineProperties.setDbStale(stale);
291 }
catch (IOException ex) {
293 stale ? Bundle.TimeLineController_setEventsDBStale_errMsgStale()
294 : Bundle.TimeLineController_setEventsDBStale_errMsgNotStale());
295 LOGGER.log(Level.SEVERE,
"Error marking the timeline db as stale.", ex);
300 return historyManager.getCanAdvance();
304 return historyManager.getCanRetreat();
308 return viewMode.getReadOnlyProperty();
317 if (this.viewMode.get() != viewMode) {
318 this.viewMode.set(viewMode);
328 return viewMode.get();
332 this.autoCase = autoCase;
333 this.perCaseTimelineProperties =
new PerCaseTimelineProperties(autoCase);
334 eventsDBStale.set(perCaseTimelineProperties.isDBStale());
335 eventsRepository =
new EventsRepository(autoCase, currentParams.getReadOnlyProperty());
343 historyManager.currentState().addListener((Observable observable) -> {
344 ZoomParams historyManagerParams = historyManager.getCurrentState();
346 currentParams.set(historyManagerParams);
350 InitialZoomState =
new ZoomParams(filteredEvents.getSpanningInterval(),
352 filteredEvents.filterProperty().get(),
354 historyManager.advance(InitialZoomState);
357 viewMode.addListener(observable -> selectEventIDs(Collections.emptySet()));
364 return filteredEvents;
368 pushFilters(filteredEvents.getDefaultFilter());
372 Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval();
373 advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval));
376 private final ObservableSet<TimeLineEvent> pinnedEvents = FXCollections.observableSet();
377 private final ObservableSet<TimeLineEvent> pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents);
380 pinnedEvents.add(event);
384 pinnedEvents.removeIf(event::equals);
388 return pinnedEventsUnmodifiable;
413 "TimeLineController.setIngestRunning.errMsgRunning=Failed to mark the timeline db as populated while ingest was running. Some results may be out of date or missing.",
414 "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."})
419 if (promptDialogManager.bringCurrentDialogToFront()) {
424 if (ingestRunning && promptDialogManager.confirmDuringIngest() ==
false) {
430 rebuildRepositoryTask = repoBuilder.apply(
new Consumer<Worker.State>() {
432 public void accept(Worker.State newSate) {
441 perCaseTimelineProperties.setIngestRunning(ingestRunning);
442 }
catch (IOException ex) {
444 ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
445 : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
446 LOGGER.log(Level.SEVERE,
"Error marking the ingest state while the timeline db was populated.", ex);
448 if (markDBNotStale) {
449 setEventsDBStale(
false);
450 filteredEvents.postDBUpdated();
452 if (file == null && artifact == null) {
457 ShowInTimelineDialog showInTimelineDilaog =
461 Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
462 dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
464 showInListView(viewInTimelineRequestedEvent);
470 setEventsDBStale(
true);
480 promptDialogManager.showDBPopulationProgressDialog(rebuildRepositoryTask);
488 public
void rebuildRepo() {
489 rebuildRepo(null, null);
501 private
void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) {
502 rebuildRepoHelper(eventsRepository::rebuildRepository,
true, file, artifact);
514 private
void rebuildTagsTable(AbstractFile file, BlackboardArtifact artifact) {
515 rebuildRepoHelper(eventsRepository::rebuildTags,
false, file, artifact);
522 synchronized (filteredEvents) {
523 return pushTimeRange(filteredEvents.getSpanningInterval());
536 synchronized (filteredEvents) {
538 selectEventIDs(requestEvent.getEventIDs());
539 if (pushTimeRange(requestEvent.getInterval()) ==
false) {
540 eventbus.post(requestEvent);
550 public
void shutDownTimeLine() {
551 listeningToAutopsy =
false;
555 if (topComponent != null) {
556 topComponent.close();
571 void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
577 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) {
702 if (topComponent.isOpened() ==
false) {
705 topComponent.toFront();
710 topComponent.requestActive();
714 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
715 if (currentZoom == null) {
716 advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
733 Interval clampedTimeRange;
734 if (timeRange == null) {
735 clampedTimeRange = this.filteredEvents.getSpanningInterval();
737 Interval spanningInterval = this.filteredEvents.getSpanningInterval();
738 if (spanningInterval.overlaps(timeRange)) {
739 clampedTimeRange = spanningInterval.overlap(timeRange);
741 clampedTimeRange = spanningInterval;
745 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
746 if (currentZoom == null) {
747 advance(InitialZoomState.withTimeRange(clampedTimeRange));
749 }
else if (currentZoom.
hasTimeRange(clampedTimeRange) ==
false) {
767 return showFullRange();
774 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
775 if (currentZoom == null) {
776 advance(InitialZoomState.withDescrLOD(newLOD));
777 }
else if (currentZoom.
hasDescrLOD(newLOD) ==
false) {
782 @SuppressWarnings(
"AssignmentToMethodParameter")
784 timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
785 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
786 if (currentZoom == null) {
787 advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
790 }
else if (currentZoom.
hasTimeRange(timeRange) ==
false) {
798 ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
799 if (currentZoom == null) {
800 advance(InitialZoomState.withFilter(filter.
copyOf()));
801 }
else if (currentZoom.
hasFilter(filter) ==
false) {
807 historyManager.advance();
811 historyManager.retreat();
815 historyManager.advance(newState);
825 selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
826 selectedEventIDs.setAll(eventIDs);
830 final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
834 protected Collection< Long> call()
throws Exception {
836 return filteredEvents.getEventIDs(timeRange,
new TypeFilter(type));
841 protected void succeeded() {
845 selectedTimeRange.set(timeRange);
846 selectedEventIDs.setAll(
get());
849 }
catch (InterruptedException | ExecutionException ex) {
850 LOGGER.log(Level.SEVERE, getTitle() +
" Unexpected error", ex);
855 monitorTask(selectTimeAndTypeTask);
867 Platform.runLater(() -> {
870 task.stateProperty().addListener((Observable observable) -> {
871 switch (task.getState()) {
880 if (tasks.isEmpty() ==
false) {
881 taskProgress.bind(tasks.get(0).progressProperty());
882 taskMessage.bind(tasks.get(0).messageProperty());
883 taskTitle.bind(tasks.get(0).titleProperty());
889 taskProgress.bind(task.progressProperty());
890 taskMessage.bind(task.messageProperty());
891 taskTitle.bind(task.titleProperty());
892 switch (task.getState()) {
894 executor.submit(task);
903 if (tasks.isEmpty() ==
false) {
904 taskProgress.bind(tasks.get(0).progressProperty());
905 taskMessage.bind(tasks.get(0).messageProperty());
906 taskTitle.bind(tasks.get(0).titleProperty());
921 eventbus.register(o);
930 eventbus.unregister(0);
960 case CONTENT_CHANGED:
963 Platform.runLater(() -> setEventsDBStale(
true));
984 case DATA_SOURCE_ANALYSIS_COMPLETED:
986 Platform.runLater(() -> setEventsDBStale(
true));
987 filteredEvents.postAutopsyEventLocally((
AutopsyEvent) evt);
989 case DATA_SOURCE_ANALYSIS_STARTED:
1006 switch (
Case.
Events.valueOf(evt.getPropertyName())) {
1007 case BLACKBOARD_ARTIFACT_TAG_ADDED:
1010 case BLACKBOARD_ARTIFACT_TAG_DELETED:
1013 case CONTENT_TAG_ADDED:
1016 case CONTENT_TAG_DELETED:
1019 case DATA_SOURCE_ADDED:
1021 Platform.runLater(() -> setEventsDBStale(
true));
1022 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)
static Case getOpenCase()
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()
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)