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) {
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)