Autopsy  4.5.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
TimeLineController.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014-2016 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.timeline;
20 
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;
95 import org.sleuthkit.datamodel.AbstractFile;
96 import org.sleuthkit.datamodel.BlackboardArtifact;
97 
114 @NbBundle.Messages({"Timeline.dialogs.title= Timeline",
115  "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
116 public class TimeLineController {
117 
118  private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName());
119 
120  private static final ReadOnlyObjectWrapper<TimeZone> timeZone = new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
121 
122  public static ZoneId getTimeZoneID() {
123  return timeZone.get().toZoneId();
124  }
125 
126  public static DateTimeFormatter getZonedFormatter() {
127  return DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone()); //NON-NLS
128  }
129 
130  public static DateTimeZone getJodaTimeZone() {
131  return DateTimeZone.forTimeZone(getTimeZone().get());
132  }
133 
134  public static ReadOnlyObjectProperty<TimeZone> getTimeZone() {
135  return timeZone.getReadOnlyProperty();
136  }
137 
138  private final ExecutorService executor = Executors.newSingleThreadExecutor();
139 
140  private final ReadOnlyListWrapper<Task<?>> tasks = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
141 
142  private final ReadOnlyDoubleWrapper taskProgress = new ReadOnlyDoubleWrapper(-1);
143 
144  private final ReadOnlyStringWrapper taskMessage = new ReadOnlyStringWrapper();
145 
146  private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
147 
148  private final ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper();
149  private EventBus eventbus = new EventBus("TimeLineController_EventBus");
150 
157  public ReadOnlyStringProperty statusMessageProperty() {
158  return statusMessage.getReadOnlyProperty();
159  }
160 
161  public void setStatusMessage(String string) {
162  statusMessage.set(string);
163  }
164  private final Case autoCase;
165  private final PerCaseTimelineProperties perCaseTimelineProperties;
166 
168  private final ObservableList<DescriptionFilter> quickHideFilters = FXCollections.observableArrayList();
169 
170  public ObservableList<DescriptionFilter> getQuickHideFilters() {
171  return quickHideFilters;
172  }
173 
177  public Case getAutopsyCase() {
178  return autoCase;
179  }
180 
181  synchronized public ReadOnlyListProperty<Task<?>> getTasks() {
182  return tasks.getReadOnlyProperty();
183  }
184 
185  synchronized public ReadOnlyDoubleProperty taskProgressProperty() {
186  return taskProgress.getReadOnlyProperty();
187  }
188 
189  synchronized public ReadOnlyStringProperty taskMessageProperty() {
190  return taskMessage.getReadOnlyProperty();
191  }
192 
193  synchronized public ReadOnlyStringProperty taskTitleProperty() {
194  return taskTitle.getReadOnlyProperty();
195  }
196 
198  private TimeLineTopComponent topComponent;
199 
200  //are the listeners currently attached
201  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
202  private boolean listeningToAutopsy = false;
203 
204  private final PropertyChangeListener caseListener = new AutopsyCaseListener();
205  private final PropertyChangeListener ingestJobListener = new AutopsyIngestJobListener();
206  private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener();
207 
208  @GuardedBy("this")
209  private final ReadOnlyObjectWrapper<ViewMode> viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS);
210 
211  @GuardedBy("filteredEvents")
212  private final FilteredEventsModel filteredEvents;
213 
214  private final EventsRepository eventsRepository;
215 
216  @GuardedBy("this")
217  private final ZoomParams InitialZoomState;
218 
219  @GuardedBy("this")
220  private final History<ZoomParams> historyManager = new History<>();
221 
222  @GuardedBy("this")
223  private final ReadOnlyObjectWrapper<ZoomParams> currentParams = new ReadOnlyObjectWrapper<>();
224 
225  //selected events (ie shown in the result viewer)
226  @GuardedBy("this")
227  private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
228 
229  @GuardedBy("this")
230  private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
231 
232  private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
233 
234  private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
235 
241  synchronized public ObservableList<Long> getSelectedEventIDs() {
242  return selectedEventIDs;
243  }
244 
250  synchronized public ReadOnlyObjectProperty<Interval> selectedTimeRangeProperty() {
251  return selectedTimeRange.getReadOnlyProperty();
252  }
253 
259  synchronized public Interval getSelectedTimeRange() {
260  return selectedTimeRange.get();
261  }
262 
263  public ReadOnlyBooleanProperty eventsDBStaleProperty() {
264  return eventsDBStale.getReadOnlyProperty();
265  }
266 
272  public boolean isEventsDBStale() {
273  return eventsDBStale.get();
274  }
275 
281  @NbBundle.Messages({
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)
285  private void setEventsDBStale(final Boolean stale) {
286  eventsDBStale.set(stale);
287  try {
288  //persist to disk
289  perCaseTimelineProperties.setDbStale(stale);
290  } catch (IOException ex) {
291  MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
292  stale ? Bundle.TimeLineController_setEventsDBStale_errMsgStale()
293  : Bundle.TimeLineController_setEventsDBStale_errMsgNotStale());
294  LOGGER.log(Level.SEVERE, "Error marking the timeline db as stale.", ex); //NON-NLS
295  }
296  }
297 
298  synchronized public ReadOnlyBooleanProperty canAdvanceProperty() {
299  return historyManager.getCanAdvance();
300  }
301 
302  synchronized public ReadOnlyBooleanProperty canRetreatProperty() {
303  return historyManager.getCanRetreat();
304  }
305 
306  synchronized public ReadOnlyObjectProperty<ViewMode> viewModeProperty() {
307  return viewMode.getReadOnlyProperty();
308  }
309 
315  synchronized public void setViewMode(ViewMode viewMode) {
316  if (this.viewMode.get() != viewMode) {
317  this.viewMode.set(viewMode);
318  }
319  }
320 
326  synchronized public ViewMode getViewMode() {
327  return viewMode.get();
328  }
329 
330  public TimeLineController(Case autoCase) throws IOException {
331  this.autoCase = autoCase;
332  this.perCaseTimelineProperties = new PerCaseTimelineProperties(autoCase);
333  eventsDBStale.set(perCaseTimelineProperties.isDBStale());
334  eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty());
335 
336  /*
337  * as the history manager's current state changes, modify the tags
338  * filter to be in sync, and expose that as propery from
339  * TimeLineController. Do we need to do this with datasource or hash hit
340  * filters?
341  */
342  historyManager.currentState().addListener((Observable observable) -> {
343  ZoomParams historyManagerParams = historyManager.getCurrentState();
344  eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter());
345  currentParams.set(historyManagerParams);
346  });
347  filteredEvents = eventsRepository.getEventsModel();
348 
349  InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
351  filteredEvents.filterProperty().get(),
353  historyManager.advance(InitialZoomState);
354 
355  //clear the selected events when the view mode changes
356  viewMode.addListener(observable -> selectEventIDs(Collections.emptySet()));
357  }
358 
363  return filteredEvents;
364  }
365 
366  public void applyDefaultFilters() {
367  pushFilters(filteredEvents.getDefaultFilter());
368  }
369 
370  public void zoomOutToActivity() {
371  Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval();
372  advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval));
373  }
374 
375  private final ObservableSet<TimeLineEvent> pinnedEvents = FXCollections.observableSet();
376  private final ObservableSet<TimeLineEvent> pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents);
377 
378  public void pinEvent(TimeLineEvent event) {
379  pinnedEvents.add(event);
380  }
381 
382  public void unPinEvent(TimeLineEvent event) {
383  pinnedEvents.removeIf(event::equals);
384  }
385 
386  public ObservableSet<TimeLineEvent> getPinnedEvents() {
387  return pinnedEventsUnmodifiable;
388  }
389 
411  @NbBundle.Messages({
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."})
414  private void rebuildRepoHelper(Function<Consumer<Worker.State>, CancellationProgressTask<?>> repoBuilder, Boolean markDBNotStale, AbstractFile file, BlackboardArtifact artifact) {
415 
416  boolean ingestRunning = IngestManager.getInstance().isIngestRunning();
417  //if there is an existing prompt or progressdialog, just show that
418  if (promptDialogManager.bringCurrentDialogToFront()) {
419  return;
420  }
421 
422  //confirm timeline during ingest
423  if (ingestRunning && promptDialogManager.confirmDuringIngest() == false) {
424  return; //if they cancel, do nothing.
425  }
426 
427  //get a task that rebuilds the repo with the below state listener attached
428  final CancellationProgressTask<?> rebuildRepositoryTask;
429  rebuildRepositoryTask = repoBuilder.apply(new Consumer<Worker.State>() {
430  @Override
431  public void accept(Worker.State newSate) {
432  //this will be on JFX thread
433  switch (newSate) {
434  case SUCCEEDED:
435  /*
436  * Record if ingest was running the last time the db was
437  * rebuilt, and hence it might stale.
438  */
439  try {
440  perCaseTimelineProperties.setIngestRunning(ingestRunning);
441  } catch (IOException ex) {
442  MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
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); //NON-NLS
446  }
447  if (markDBNotStale) {
448  setEventsDBStale(false);
449  filteredEvents.postDBUpdated();
450  }
451  if (file == null && artifact == null) {
452  SwingUtilities.invokeLater(TimeLineController.this::showWindow);
454  } else {
455  //prompt user to pick specific event and time range
456  ShowInTimelineDialog showInTimelineDilaog =
457  (file == null)
458  ? new ShowInTimelineDialog(TimeLineController.this, artifact)
459  : new ShowInTimelineDialog(TimeLineController.this, file);
460  Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
461  dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
462  SwingUtilities.invokeLater(TimeLineController.this::showWindow);
463  showInListView(viewInTimelineRequestedEvent); //show requested event in list view
464  });
465  }
466  break;
467  case FAILED:
468  case CANCELLED:
469  setEventsDBStale(true);
470  break;
471  }
472  }
473  });
474 
475  /*
476  * Since both of the expected repoBuilders start the back ground task,
477  * all we have to do is show progress dialog for the task
478  */
479  promptDialogManager.showDBPopulationProgressDialog(rebuildRepositoryTask);
480  }
481 
487  public void rebuildRepo() {
488  rebuildRepo(null, null);
489  }
490 
500  private void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) {
501  rebuildRepoHelper(eventsRepository::rebuildRepository, true, file, artifact);
502  }
503 
513  private void rebuildTagsTable(AbstractFile file, BlackboardArtifact artifact) {
514  rebuildRepoHelper(eventsRepository::rebuildTags, false, file, artifact);
515  }
516 
520  private boolean showFullRange() {
521  synchronized (filteredEvents) {
522  return pushTimeRange(filteredEvents.getSpanningInterval());
523  }
524  }
525 
534  private void showInListView(ViewInTimelineRequestedEvent requestEvent) {
535  synchronized (filteredEvents) {
536  setViewMode(ViewMode.LIST);
537  selectEventIDs(requestEvent.getEventIDs());
538  if (pushTimeRange(requestEvent.getInterval()) == false) {
539  eventbus.post(requestEvent);
540  }
541  }
542  }
543 
549  public void shutDownTimeLine() {
550  listeningToAutopsy = false;
553  Case.removePropertyChangeListener(caseListener);
554  if (topComponent != null) {
555  topComponent.close();
556  topComponent = null;
557  }
558  OpenTimelineAction.invalidateController();
559  }
560 
570  void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
571  // listen for case changes (specifically images being added, and case changes).
572  if (Case.isCaseOpen() && !listeningToAutopsy) {
575  Case.addPropertyChangeListener(caseListener);
576  listeningToAutopsy = true;
577  }
578  Platform.runLater(() -> promptForRebuild(file, artifact));
579  }
580 
591  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
592  private void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) {
593  //if there is an existing prompt or progressdialog, just show that
594  if (promptDialogManager.bringCurrentDialogToFront()) {
595  return;
596  }
597 
598  //if the repo is empty just (re)build it with out asking, the user can always cancel part way through
599  if (eventsRepository.countAllEvents() == 0) {
600  rebuildRepo(file, artifact);
601  return;
602  }
603 
604  //if necessary prompt user with reasons to rebuild
605  List<String> rebuildReasons = getRebuildReasons();
606  if (false == rebuildReasons.isEmpty()) {
607  if (promptDialogManager.confirmRebuild(rebuildReasons)) {
608  rebuildRepo(file, artifact);
609  return;
610  }
611  }
612 
613  /*
614  * if the repo was not rebuilt, at a minimum rebuild the tags which may
615  * have been updated without our knowing it, since we can't/aren't
616  * checking them. This should at least be quick.
617  *
618  * //TODO: can we check the tags to see if we need to do this?
619  */
620  rebuildTagsTable(file, artifact);
621  }
622 
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."})
637  private List<String> getRebuildReasons() {
638  ArrayList<String> rebuildReasons = new ArrayList<>();
639 
640  try {
641  //if ingest was running during last rebuild, prompt to rebuild
642  if (perCaseTimelineProperties.wasIngestRunning()) {
643  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning());
644  }
645 
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); //NON-NLS
648  MessageNotifyUtil.Notify.error(Bundle.TimeLineController_errorTitle(),
649  Bundle.TimeLineController_outOfDate_errorMessage());
650  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError());
651  }
652  //if the events db is stale, prompt to rebuild
653  if (isEventsDBStale()) {
654  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDate());
655  }
656  // if the TL DB schema has been upgraded since last time TL ran, prompt for rebuild
657  if (eventsRepository.hasNewColumns() == false) {
658  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema());
659  }
660  return rebuildReasons;
661  }
662 
670  synchronized public void pushPeriod(ReadablePeriod period) {
671  synchronized (filteredEvents) {
672  pushTimeRange(IntervalUtils.getIntervalAroundMiddle(filteredEvents.getTimeRange(), period));
673  }
674  }
675 
676  synchronized public void pushZoomOutTime() {
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));
682  }
683 
684  synchronized public void pushZoomInTime() {
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));
690  }
691 
697  synchronized private void showWindow() {
698  if (topComponent == null) {
699  topComponent = new TimeLineTopComponent(this);
700  }
701  if (topComponent.isOpened() == false) {
702  topComponent.open();
703  }
704  topComponent.toFront();
705  /*
706  * Make this top component active so its ExplorerManager's lookup gets
707  * proxied in Utilities.actionsGlobalContext()
708  */
709  topComponent.requestActive();
710  }
711 
712  synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
713  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
714  if (currentZoom == null) {
715  advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
716  } else if (currentZoom.hasTypeZoomLevel(typeZoomeLevel) == false) {
717  advance(currentZoom.withTypeZoomLevel(typeZoomeLevel));
718  }
719  }
720 
730  synchronized public boolean pushTimeRange(Interval timeRange) {
731  //clamp timerange to case
732  Interval clampedTimeRange;
733  if (timeRange == null) {
734  clampedTimeRange = this.filteredEvents.getSpanningInterval();
735  } else {
736  Interval spanningInterval = this.filteredEvents.getSpanningInterval();
737  if (spanningInterval.overlaps(timeRange)) {
738  clampedTimeRange = spanningInterval.overlap(timeRange);
739  } else {
740  clampedTimeRange = spanningInterval;
741  }
742  }
743 
744  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
745  if (currentZoom == null) {
746  advance(InitialZoomState.withTimeRange(clampedTimeRange));
747  return true;
748  } else if (currentZoom.hasTimeRange(clampedTimeRange) == false) {
749  advance(currentZoom.withTimeRange(clampedTimeRange));
750  return true;
751  } else {
752  return false;
753  }
754  }
755 
764  synchronized public boolean pushTimeUnit(TimeUnits timeUnit) {
765  if (timeUnit == TimeUnits.FOREVER) {
766  return showFullRange();
767  } else {
768  return pushTimeRange(IntervalUtils.getIntervalAroundMiddle(filteredEvents.getTimeRange(), timeUnit.getPeriod()));
769  }
770  }
771 
772  synchronized public void pushDescrLOD(DescriptionLoD newLOD) {
773  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
774  if (currentZoom == null) {
775  advance(InitialZoomState.withDescrLOD(newLOD));
776  } else if (currentZoom.hasDescrLOD(newLOD) == false) {
777  advance(currentZoom.withDescrLOD(newLOD));
778  }
779  }
780 
781  @SuppressWarnings("AssignmentToMethodParameter") //clamp timerange to case
782  synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
783  timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
784  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
785  if (currentZoom == null) {
786  advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
787  } else if (currentZoom.hasTimeRange(timeRange) == false && currentZoom.hasTypeZoomLevel(typeZoom) == false) {
788  advance(currentZoom.withTimeAndType(timeRange, typeZoom));
789  } else if (currentZoom.hasTimeRange(timeRange) == false) {
790  advance(currentZoom.withTimeRange(timeRange));
791  } else if (currentZoom.hasTypeZoomLevel(typeZoom) == false) {
792  advance(currentZoom.withTypeZoomLevel(typeZoom));
793  }
794  }
795 
796  synchronized public void pushFilters(RootFilter filter) {
797  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
798  if (currentZoom == null) {
799  advance(InitialZoomState.withFilter(filter.copyOf()));
800  } else if (currentZoom.hasFilter(filter) == false) {
801  advance(currentZoom.withFilter(filter.copyOf()));
802  }
803  }
804 
805  synchronized public void advance() {
806  historyManager.advance();
807  }
808 
809  synchronized public void retreat() {
810  historyManager.retreat();
811  }
812 
813  synchronized private void advance(ZoomParams newState) {
814  historyManager.advance(newState);
815  }
816 
823  synchronized public void selectEventIDs(Collection<Long> eventIDs) {
824  selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
825  selectedEventIDs.setAll(eventIDs);
826  }
827 
828  public void selectTimeAndType(Interval interval, EventType type) {
829  final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
830 
831  final LoggedTask<Collection<Long>> selectTimeAndTypeTask = new LoggedTask<Collection<Long>>("Select Time and Type", true) { //NON-NLS
832  @Override
833  protected Collection< Long> call() throws Exception {
834  synchronized (TimeLineController.this) {
835  return filteredEvents.getEventIDs(timeRange, new TypeFilter(type));
836  }
837  }
838 
839  @Override
840  protected void succeeded() {
841  super.succeeded();
842  try {
843  synchronized (TimeLineController.this) {
844  selectedTimeRange.set(timeRange);
845  selectedEventIDs.setAll(get());
846 
847  }
848  } catch (InterruptedException | ExecutionException ex) {
849  LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); //NON-NLS
850  }
851  }
852  };
853 
854  monitorTask(selectTimeAndTypeTask);
855  }
856 
863  synchronized public void monitorTask(final Task<?> task) {
864  //TODO: refactor this to use JavaFX Service? -jm
865  if (task != null) {
866  Platform.runLater(() -> {
867 
868  //is this actually threadsafe, could we get a finished task stuck in the list?
869  task.stateProperty().addListener((Observable observable) -> {
870  switch (task.getState()) {
871  case READY:
872  case RUNNING:
873  case SCHEDULED:
874  break;
875  case SUCCEEDED:
876  case CANCELLED:
877  case FAILED:
878  tasks.remove(task);
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());
883  }
884  break;
885  }
886  });
887  tasks.add(task);
888  taskProgress.bind(task.progressProperty());
889  taskMessage.bind(task.messageProperty());
890  taskTitle.bind(task.titleProperty());
891  switch (task.getState()) {
892  case READY:
893  executor.submit(task);
894  break;
895  case SCHEDULED:
896  case RUNNING:
897 
898  case SUCCEEDED:
899  case CANCELLED:
900  case FAILED:
901  tasks.remove(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());
906  }
907  break;
908  }
909  });
910  }
911  }
912 
919  synchronized public void registerForEvents(Object o) {
920  eventbus.register(o);
921  }
922 
928  synchronized public void unRegisterForEvents(Object o) {
929  eventbus.unregister(0);
930  }
931 
932  static synchronized public void setTimeZone(TimeZone timeZone) {
933  TimeLineController.timeZone.set(timeZone);
934 
935  }
936 
940  @Immutable
941  private class AutopsyIngestModuleListener implements PropertyChangeListener {
942 
943  @Override
944  public void propertyChange(PropertyChangeEvent evt) {
951  try {
953  } catch (IllegalStateException notUsed) {
954  // Case is closed, do nothing.
955  return;
956  }
957 
958  switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
959  case CONTENT_CHANGED:
960  case DATA_ADDED:
961  //since black board artifacts or new derived content have been added, the DB is stale.
962  Platform.runLater(() -> setEventsDBStale(true));
963  break;
964  case FILE_DONE:
965  /*
966  * Do nothing, since we have captured all new results in
967  * CONTENT_CHANGED and DATA_ADDED or the IngestJob listener,
968  */
969  break;
970  }
971  }
972  }
973 
977  @Immutable
978  private class AutopsyIngestJobListener implements PropertyChangeListener {
979 
980  @Override
981  public void propertyChange(PropertyChangeEvent evt) {
982  switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
983  case DATA_SOURCE_ANALYSIS_COMPLETED:
984  //mark db stale, and prompt to rebuild
985  Platform.runLater(() -> setEventsDBStale(true));
986  filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
987  break;
988  case DATA_SOURCE_ANALYSIS_STARTED:
989  case CANCELLED:
990  case COMPLETED:
991  case STARTED:
992  break;
993  }
994  }
995  }
996 
1000  @Immutable
1001  private class AutopsyCaseListener implements PropertyChangeListener {
1002 
1003  @Override
1004  public void propertyChange(PropertyChangeEvent evt) {
1005  switch (Case.Events.valueOf(evt.getPropertyName())) {
1006  case BLACKBOARD_ARTIFACT_TAG_ADDED:
1007  executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
1008  break;
1009  case BLACKBOARD_ARTIFACT_TAG_DELETED:
1010  executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
1011  break;
1012  case CONTENT_TAG_ADDED:
1013  executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
1014  break;
1015  case CONTENT_TAG_DELETED:
1016  executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
1017  break;
1018  case DATA_SOURCE_ADDED:
1019  //mark db stale, and prompt to rebuild
1020  Platform.runLater(() -> setEventsDBStale(true));
1021  filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
1022  break;
1023  case CURRENT_CASE:
1024  //close timeline on case changes.
1025  SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
1026  break;
1027  }
1028  }
1029  }
1030 }
synchronized ReadOnlyDoubleProperty taskProgressProperty()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
ZoomParams withDescrLOD(DescriptionLoD descrLOD)
Definition: ZoomParams.java:74
static synchronized IngestManager getInstance()
ZoomParams withTypeZoomLevel(EventTypeZoomLevel zoomLevel)
Definition: ZoomParams.java:66
static final ReadOnlyObjectWrapper< TimeZone > timeZone
static ReadOnlyObjectProperty< TimeZone > getTimeZone()
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:394
void rebuildRepoHelper(Function< Consumer< Worker.State >, CancellationProgressTask<?>> repoBuilder, Boolean markDBNotStale, AbstractFile file, BlackboardArtifact artifact)
synchronized void setViewMode(ViewMode viewMode)
synchronized void selectEventIDs(Collection< Long > eventIDs)
synchronized ReadOnlyObjectProperty< Interval > selectedTimeRangeProperty()
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)
Definition: ZoomParams.java:86
synchronized ReadOnlyStringProperty taskMessageProperty()
boolean hasDescrLOD(DescriptionLoD newLOD)
Definition: ZoomParams.java:94
static Interval getIntervalAroundMiddle(Interval interval, ReadablePeriod period)
ZoomParams withTimeRange(Interval timeRange)
Definition: ZoomParams.java:70
void addIngestJobEventListener(final PropertyChangeListener listener)
static synchronized void setTimeZone(TimeZone timeZone)
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:382
synchronized void advance(ZoomParams newState)
ZoomParams withTimeAndType(Interval timeRange, EventTypeZoomLevel zoomLevel)
Definition: ZoomParams.java:62
synchronized void pushDescrLOD(DescriptionLoD newLOD)
synchronized void pushFilters(RootFilter filter)
final PerCaseTimelineProperties perCaseTimelineProperties
static void error(String title, String message)
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
synchronized ReadOnlyBooleanProperty canAdvanceProperty()
ZoomParams withFilter(RootFilter filter)
Definition: ZoomParams.java:78
synchronized ReadOnlyListProperty< Task<?> > getTasks()
synchronized ReadOnlyBooleanProperty canRetreatProperty()
synchronized void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel)

Copyright © 2012-2016 Basis Technology. Generated on: Tue Feb 20 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.