Autopsy  4.1
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 
579  Platform.runLater(() -> promptForRebuild(file, artifact));
580  }
581 
592  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
593  private void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) {
594  //if there is an existing prompt or progressdialog, just show that
595  if (promptDialogManager.bringCurrentDialogToFront()) {
596  return;
597  }
598 
599  //if the repo is empty just (re)build it with out asking, the user can always cancel part way through
600  if (eventsRepository.countAllEvents() == 0) {
601  rebuildRepo(file, artifact);
602  return;
603  }
604 
605  //if necessary prompt user with reasons to rebuild
606  List<String> rebuildReasons = getRebuildReasons();
607  if (false == rebuildReasons.isEmpty()) {
608  if (promptDialogManager.confirmRebuild(rebuildReasons)) {
609  rebuildRepo(file, artifact);
610  return;
611  }
612  }
613 
614  /*
615  * if the repo was not rebuilt, at a minimum rebuild the tags which may
616  * have been updated without our knowing it, since we can't/aren't
617  * checking them. This should at least be quick.
618  *
619  * //TODO: can we check the tags to see if we need to do this?
620  */
621  rebuildTagsTable(file, artifact);
622  }
623 
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."})
638  private List<String> getRebuildReasons() {
639  ArrayList<String> rebuildReasons = new ArrayList<>();
640 
641  try {
642  //if ingest was running during last rebuild, prompt to rebuild
643  if (perCaseTimelineProperties.wasIngestRunning()) {
644  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning());
645  }
646 
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); //NON-NLS
649  MessageNotifyUtil.Notify.error(Bundle.TimeLineController_errorTitle(),
650  Bundle.TimeLineController_outOfDate_errorMessage());
651  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError());
652  }
653  //if the events db is stale, prompt to rebuild
654  if (isEventsDBStale()) {
655  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDate());
656  }
657  // if the TL DB schema has been upgraded since last time TL ran, prompt for rebuild
658  if (eventsRepository.hasNewColumns() == false) {
659  rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema());
660  }
661  return rebuildReasons;
662  }
663 
671  synchronized public void pushPeriod(ReadablePeriod period) {
672  synchronized (filteredEvents) {
673  pushTimeRange(IntervalUtils.getIntervalAroundMiddle(filteredEvents.getTimeRange(), period));
674  }
675  }
676 
677  synchronized public void pushZoomOutTime() {
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));
683  }
684 
685  synchronized public void pushZoomInTime() {
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));
691  }
692 
698  synchronized private void showWindow() {
699  if (topComponent == null) {
700  topComponent = new TimeLineTopComponent(this);
701  }
702  topComponent.open();
703  topComponent.toFront();
704  /*
705  * Make this top component active so its ExplorerManager's lookup gets
706  * proxied in Utilities.actionsGlobalContext()
707  */
708  topComponent.requestActive();
709  }
710 
711  synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
712  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
713  if (currentZoom == null) {
714  advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
715  } else if (currentZoom.hasTypeZoomLevel(typeZoomeLevel) == false) {
716  advance(currentZoom.withTypeZoomLevel(typeZoomeLevel));
717  }
718  }
719 
729  synchronized public boolean pushTimeRange(Interval timeRange) {
730  //clamp timerange to case
731  Interval clampedTimeRange;
732  if (timeRange == null) {
733  clampedTimeRange = this.filteredEvents.getSpanningInterval();
734  } else {
735  Interval spanningInterval = this.filteredEvents.getSpanningInterval();
736  if (spanningInterval.overlaps(timeRange)) {
737  clampedTimeRange = spanningInterval.overlap(timeRange);
738  } else {
739  clampedTimeRange = spanningInterval;
740  }
741  }
742 
743  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
744  if (currentZoom == null) {
745  advance(InitialZoomState.withTimeRange(clampedTimeRange));
746  return true;
747  } else if (currentZoom.hasTimeRange(clampedTimeRange) == false) {
748  advance(currentZoom.withTimeRange(clampedTimeRange));
749  return true;
750  } else {
751  return false;
752  }
753  }
754 
763  synchronized public boolean pushTimeUnit(TimeUnits timeUnit) {
764  if (timeUnit == TimeUnits.FOREVER) {
765  return showFullRange();
766  } else {
767  return pushTimeRange(IntervalUtils.getIntervalAroundMiddle(filteredEvents.getTimeRange(), timeUnit.getPeriod()));
768  }
769  }
770 
771  synchronized public void pushDescrLOD(DescriptionLoD newLOD) {
772  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
773  if (currentZoom == null) {
774  advance(InitialZoomState.withDescrLOD(newLOD));
775  } else if (currentZoom.hasDescrLOD(newLOD) == false) {
776  advance(currentZoom.withDescrLOD(newLOD));
777  }
778  }
779 
780  @SuppressWarnings("AssignmentToMethodParameter") //clamp timerange to case
781  synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
782  timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
783  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
784  if (currentZoom == null) {
785  advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
786  } else if (currentZoom.hasTimeRange(timeRange) == false && currentZoom.hasTypeZoomLevel(typeZoom) == false) {
787  advance(currentZoom.withTimeAndType(timeRange, typeZoom));
788  } else if (currentZoom.hasTimeRange(timeRange) == false) {
789  advance(currentZoom.withTimeRange(timeRange));
790  } else if (currentZoom.hasTypeZoomLevel(typeZoom) == false) {
791  advance(currentZoom.withTypeZoomLevel(typeZoom));
792  }
793  }
794 
795  synchronized public void pushFilters(RootFilter filter) {
796  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
797  if (currentZoom == null) {
798  advance(InitialZoomState.withFilter(filter.copyOf()));
799  } else if (currentZoom.hasFilter(filter) == false) {
800  advance(currentZoom.withFilter(filter.copyOf()));
801  }
802  }
803 
804  synchronized public void advance() {
805  historyManager.advance();
806  }
807 
808  synchronized public void retreat() {
809  historyManager.retreat();
810  }
811 
812  synchronized private void advance(ZoomParams newState) {
813  historyManager.advance(newState);
814  }
815 
822  synchronized public void selectEventIDs(Collection<Long> eventIDs) {
823  selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
824  selectedEventIDs.setAll(eventIDs);
825  }
826 
827  public void selectTimeAndType(Interval interval, EventType type) {
828  final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
829 
830  final LoggedTask<Collection<Long>> selectTimeAndTypeTask = new LoggedTask<Collection<Long>>("Select Time and Type", true) { //NON-NLS
831  @Override
832  protected Collection< Long> call() throws Exception {
833  synchronized (TimeLineController.this) {
834  return filteredEvents.getEventIDs(timeRange, new TypeFilter(type));
835  }
836  }
837 
838  @Override
839  protected void succeeded() {
840  super.succeeded();
841  try {
842  synchronized (TimeLineController.this) {
843  selectedTimeRange.set(timeRange);
844  selectedEventIDs.setAll(get());
845 
846  }
847  } catch (InterruptedException | ExecutionException ex) {
848  LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); //NON-NLS
849  }
850  }
851  };
852 
853  monitorTask(selectTimeAndTypeTask);
854  }
855 
862  synchronized public void monitorTask(final Task<?> task) {
863  //TODO: refactor this to use JavaFX Service? -jm
864  if (task != null) {
865  Platform.runLater(() -> {
866 
867  //is this actually threadsafe, could we get a finished task stuck in the list?
868  task.stateProperty().addListener((Observable observable) -> {
869  switch (task.getState()) {
870  case READY:
871  case RUNNING:
872  case SCHEDULED:
873  break;
874  case SUCCEEDED:
875  case CANCELLED:
876  case FAILED:
877  tasks.remove(task);
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());
882  }
883  break;
884  }
885  });
886  tasks.add(task);
887  taskProgress.bind(task.progressProperty());
888  taskMessage.bind(task.messageProperty());
889  taskTitle.bind(task.titleProperty());
890  switch (task.getState()) {
891  case READY:
892  executor.submit(task);
893  break;
894  case SCHEDULED:
895  case RUNNING:
896 
897  case SUCCEEDED:
898  case CANCELLED:
899  case FAILED:
900  tasks.remove(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());
905  }
906  break;
907  }
908  });
909  }
910  }
911 
918  synchronized public void registerForEvents(Object o) {
919  eventbus.register(o);
920  }
921 
927  synchronized public void unRegisterForEvents(Object o) {
928  eventbus.unregister(0);
929  }
930 
931  static synchronized public void setTimeZone(TimeZone timeZone) {
932  TimeLineController.timeZone.set(timeZone);
933 
934  }
935 
939  @Immutable
940  private class AutopsyIngestModuleListener implements PropertyChangeListener {
941 
942  @Override
943  public void propertyChange(PropertyChangeEvent evt) {
950  try {
952  } catch (IllegalStateException notUsed) {
953  // Case is closed, do nothing.
954  return;
955  }
956 
957  switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
958  case CONTENT_CHANGED:
959  case DATA_ADDED:
960  //since black board artifacts or new derived content have been added, the DB is stale.
961  Platform.runLater(() -> setEventsDBStale(true));
962  break;
963  case FILE_DONE:
964  /*
965  * Do nothing, since we have captured all new results in
966  * CONTENT_CHANGED and DATA_ADDED or the IngestJob listener,
967  */
968  break;
969  }
970  }
971  }
972 
976  @Immutable
977  private class AutopsyIngestJobListener implements PropertyChangeListener {
978 
979  @Override
980  public void propertyChange(PropertyChangeEvent evt) {
981  switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
982  case DATA_SOURCE_ANALYSIS_COMPLETED:
983  //mark db stale, and prompt to rebuild
984  Platform.runLater(() -> setEventsDBStale(true));
985  filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
986  break;
987  case DATA_SOURCE_ANALYSIS_STARTED:
988  case CANCELLED:
989  case COMPLETED:
990  case STARTED:
991  break;
992  }
993  }
994  }
995 
999  @Immutable
1000  private class AutopsyCaseListener implements PropertyChangeListener {
1001 
1002  @Override
1003  public void propertyChange(PropertyChangeEvent evt) {
1004  switch (Case.Events.valueOf(evt.getPropertyName())) {
1005  case BLACKBOARD_ARTIFACT_TAG_ADDED:
1006  executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
1007  break;
1008  case BLACKBOARD_ARTIFACT_TAG_DELETED:
1009  executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
1010  break;
1011  case CONTENT_TAG_ADDED:
1012  executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
1013  break;
1014  case CONTENT_TAG_DELETED:
1015  executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
1016  break;
1017  case DATA_SOURCE_ADDED:
1018  //mark db stale, and prompt to rebuild
1019  Platform.runLater(() -> setEventsDBStale(true));
1020  filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
1021  break;
1022  case CURRENT_CASE:
1023  //close timeline on case changes.
1024  SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
1025  break;
1026  }
1027  }
1028  }
1029 }
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:318
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:306
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:161
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: Mon Jan 2 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.