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)