Autopsy  4.9.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-2018 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;
96 import org.sleuthkit.datamodel.AbstractFile;
97 import org.sleuthkit.datamodel.BlackboardArtifact;
98 
115 @NbBundle.Messages({"Timeline.dialogs.title= Timeline",
116  "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
117 public class TimeLineController {
118 
119  private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName());
120 
121  private static final ReadOnlyObjectWrapper<TimeZone> timeZone = new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
122 
123  public static ZoneId getTimeZoneID() {
124  return timeZone.get().toZoneId();
125  }
126 
127  public static DateTimeFormatter getZonedFormatter() {
128  return DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone()); //NON-NLS
129  }
130 
131  public static DateTimeZone getJodaTimeZone() {
132  return DateTimeZone.forTimeZone(getTimeZone().get());
133  }
134 
135  public static ReadOnlyObjectProperty<TimeZone> getTimeZone() {
136  return timeZone.getReadOnlyProperty();
137  }
138 
139  private final ExecutorService executor = Executors.newSingleThreadExecutor();
140 
141  private final ReadOnlyListWrapper<Task<?>> tasks = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
142 
143  private final ReadOnlyDoubleWrapper taskProgress = new ReadOnlyDoubleWrapper(-1);
144 
145  private final ReadOnlyStringWrapper taskMessage = new ReadOnlyStringWrapper();
146 
147  private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
148 
149  private final ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper();
150  private EventBus eventbus = new EventBus("TimeLineController_EventBus");
151 
158  public ReadOnlyStringProperty statusMessageProperty() {
159  return statusMessage.getReadOnlyProperty();
160  }
161 
162  public void setStatusMessage(String string) {
163  statusMessage.set(string);
164  }
165  private final Case autoCase;
166  private final PerCaseTimelineProperties perCaseTimelineProperties;
167 
169  private final ObservableList<DescriptionFilter> quickHideFilters = FXCollections.observableArrayList();
170 
171  public ObservableList<DescriptionFilter> getQuickHideFilters() {
172  return quickHideFilters;
173  }
174 
178  public Case getAutopsyCase() {
179  return autoCase;
180  }
181 
182  synchronized public ReadOnlyListProperty<Task<?>> getTasks() {
183  return tasks.getReadOnlyProperty();
184  }
185 
186  synchronized public ReadOnlyDoubleProperty taskProgressProperty() {
187  return taskProgress.getReadOnlyProperty();
188  }
189 
190  synchronized public ReadOnlyStringProperty taskMessageProperty() {
191  return taskMessage.getReadOnlyProperty();
192  }
193 
194  synchronized public ReadOnlyStringProperty taskTitleProperty() {
195  return taskTitle.getReadOnlyProperty();
196  }
197 
199  private TimeLineTopComponent topComponent;
200 
201  //are the listeners currently attached
202  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
203  private boolean listeningToAutopsy = false;
204 
205  private final PropertyChangeListener caseListener = new AutopsyCaseListener();
206  private final PropertyChangeListener ingestJobListener = new AutopsyIngestJobListener();
207  private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener();
208 
209  @GuardedBy("this")
210  private final ReadOnlyObjectWrapper<ViewMode> viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS);
211 
212  @GuardedBy("filteredEvents")
213  private final FilteredEventsModel filteredEvents;
214 
215  private final EventsRepository eventsRepository;
216 
217  @GuardedBy("this")
218  private final ZoomParams InitialZoomState;
219 
220  @GuardedBy("this")
221  private final History<ZoomParams> historyManager = new History<>();
222 
223  @GuardedBy("this")
224  private final ReadOnlyObjectWrapper<ZoomParams> currentParams = new ReadOnlyObjectWrapper<>();
225 
226  //selected events (ie shown in the result viewer)
227  @GuardedBy("this")
228  private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
229 
230  @GuardedBy("this")
231  private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
232 
233  private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
234 
235  private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
236 
242  synchronized public ObservableList<Long> getSelectedEventIDs() {
243  return selectedEventIDs;
244  }
245 
251  synchronized public ReadOnlyObjectProperty<Interval> selectedTimeRangeProperty() {
252  return selectedTimeRange.getReadOnlyProperty();
253  }
254 
260  synchronized public Interval getSelectedTimeRange() {
261  return selectedTimeRange.get();
262  }
263 
264  public ReadOnlyBooleanProperty eventsDBStaleProperty() {
265  return eventsDBStale.getReadOnlyProperty();
266  }
267 
273  public boolean isEventsDBStale() {
274  return eventsDBStale.get();
275  }
276 
282  @NbBundle.Messages({
283  "TimeLineController.setEventsDBStale.errMsgStale=Failed to mark the timeline db as stale. Some results may be out of date or missing.",
284  "TimeLineController.setEventsDBStale.errMsgNotStale=Failed to mark the timeline db as not stale. Some results may be out of date or missing."})
285  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
286  private void setEventsDBStale(final Boolean stale) {
287  eventsDBStale.set(stale);
288  try {
289  //persist to disk
290  perCaseTimelineProperties.setDbStale(stale);
291  } catch (IOException ex) {
292  MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
293  stale ? Bundle.TimeLineController_setEventsDBStale_errMsgStale()
294  : Bundle.TimeLineController_setEventsDBStale_errMsgNotStale());
295  LOGGER.log(Level.SEVERE, "Error marking the timeline db as stale.", ex); //NON-NLS
296  }
297  }
298 
299  synchronized public ReadOnlyBooleanProperty canAdvanceProperty() {
300  return historyManager.getCanAdvance();
301  }
302 
303  synchronized public ReadOnlyBooleanProperty canRetreatProperty() {
304  return historyManager.getCanRetreat();
305  }
306 
307  synchronized public ReadOnlyObjectProperty<ViewMode> viewModeProperty() {
308  return viewMode.getReadOnlyProperty();
309  }
310 
316  synchronized public void setViewMode(ViewMode viewMode) {
317  if (this.viewMode.get() != viewMode) {
318  this.viewMode.set(viewMode);
319  }
320  }
321 
327  synchronized public ViewMode getViewMode() {
328  return viewMode.get();
329  }
330 
331  public TimeLineController(Case autoCase) throws IOException {
332  this.autoCase = autoCase;
333  this.perCaseTimelineProperties = new PerCaseTimelineProperties(autoCase);
334  eventsDBStale.set(perCaseTimelineProperties.isDBStale());
335  eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty());
336 
337  /*
338  * as the history manager's current state changes, modify the tags
339  * filter to be in sync, and expose that as propery from
340  * TimeLineController. Do we need to do this with datasource or hash hit
341  * filters?
342  */
343  historyManager.currentState().addListener((Observable observable) -> {
344  ZoomParams historyManagerParams = historyManager.getCurrentState();
345  eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter());
346  currentParams.set(historyManagerParams);
347  });
348  filteredEvents = eventsRepository.getEventsModel();
349 
350  InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
352  filteredEvents.filterProperty().get(),
354  historyManager.advance(InitialZoomState);
355 
356  //clear the selected events when the view mode changes
357  viewMode.addListener(observable -> selectEventIDs(Collections.emptySet()));
358  }
359 
364  return filteredEvents;
365  }
366 
367  public void applyDefaultFilters() {
368  pushFilters(filteredEvents.getDefaultFilter());
369  }
370 
371  public void zoomOutToActivity() {
372  Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval();
373  advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval));
374  }
375 
376  private final ObservableSet<TimeLineEvent> pinnedEvents = FXCollections.observableSet();
377  private final ObservableSet<TimeLineEvent> pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents);
378 
379  public void pinEvent(TimeLineEvent event) {
380  pinnedEvents.add(event);
381  }
382 
383  public void unPinEvent(TimeLineEvent event) {
384  pinnedEvents.removeIf(event::equals);
385  }
386 
387  public ObservableSet<TimeLineEvent> getPinnedEvents() {
388  return pinnedEventsUnmodifiable;
389  }
390 
412  @NbBundle.Messages({
413  "TimeLineController.setIngestRunning.errMsgRunning=Failed to mark the timeline db as populated while ingest was running. Some results may be out of date or missing.",
414  "TimeLinecontroller.setIngestRunning.errMsgNotRunning=Failed to mark the timeline db as populated while ingest was not running. Some results may be out of date or missing."})
415  private void rebuildRepoHelper(Function<Consumer<Worker.State>, CancellationProgressTask<?>> repoBuilder, Boolean markDBNotStale, AbstractFile file, BlackboardArtifact artifact) {
416 
417  boolean ingestRunning = IngestManager.getInstance().isIngestRunning();
418  //if there is an existing prompt or progressdialog, just show that
419  if (promptDialogManager.bringCurrentDialogToFront()) {
420  return;
421  }
422 
423  //confirm timeline during ingest
424  if (ingestRunning && promptDialogManager.confirmDuringIngest() == false) {
425  return; //if they cancel, do nothing.
426  }
427 
428  //get a task that rebuilds the repo with the below state listener attached
429  final CancellationProgressTask<?> rebuildRepositoryTask;
430  rebuildRepositoryTask = repoBuilder.apply(new Consumer<Worker.State>() {
431  @Override
432  public void accept(Worker.State newSate) {
433  //this will be on JFX thread
434  switch (newSate) {
435  case SUCCEEDED:
436  /*
437  * Record if ingest was running the last time the db was
438  * rebuilt, and hence it might stale.
439  */
440  try {
441  perCaseTimelineProperties.setIngestRunning(ingestRunning);
442  } catch (IOException ex) {
443  MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
444  ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
445  : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
446  LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS
447  }
448  if (markDBNotStale) {
449  setEventsDBStale(false);
450  filteredEvents.postDBUpdated();
451  }
452  if (file == null && artifact == null) {
453  SwingUtilities.invokeLater(TimeLineController.this::showWindow);
455  } else {
456  //prompt user to pick specific event and time range
457  ShowInTimelineDialog showInTimelineDilaog =
458  (file == null)
459  ? new ShowInTimelineDialog(TimeLineController.this, artifact)
460  : new ShowInTimelineDialog(TimeLineController.this, file);
461  Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
462  dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
463  SwingUtilities.invokeLater(TimeLineController.this::showWindow);
464  showInListView(viewInTimelineRequestedEvent); //show requested event in list view
465  });
466  }
467  break;
468  case FAILED:
469  case CANCELLED:
470  setEventsDBStale(true);
471  break;
472  }
473  }
474  });
475 
476  /*
477  * Since both of the expected repoBuilders start the back ground task,
478  * all we have to do is show progress dialog for the task
479  */
480  promptDialogManager.showDBPopulationProgressDialog(rebuildRepositoryTask);
481  }
482 
488  public void rebuildRepo() {
489  rebuildRepo(null, null);
490  }
491 
501  private void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) {
502  rebuildRepoHelper(eventsRepository::rebuildRepository, true, file, artifact);
503  }
504 
514  private void rebuildTagsTable(AbstractFile file, BlackboardArtifact artifact) {
515  rebuildRepoHelper(eventsRepository::rebuildTags, false, file, artifact);
516  }
517 
521  private boolean showFullRange() {
522  synchronized (filteredEvents) {
523  return pushTimeRange(filteredEvents.getSpanningInterval());
524  }
525  }
526 
535  private void showInListView(ViewInTimelineRequestedEvent requestEvent) {
536  synchronized (filteredEvents) {
537  setViewMode(ViewMode.LIST);
538  selectEventIDs(requestEvent.getEventIDs());
539  if (pushTimeRange(requestEvent.getInterval()) == false) {
540  eventbus.post(requestEvent);
541  }
542  }
543  }
544 
550  public void shutDownTimeLine() {
551  listeningToAutopsy = false;
554  Case.removePropertyChangeListener(caseListener);
555  if (topComponent != null) {
556  topComponent.close();
557  topComponent = null;
558  }
559  OpenTimelineAction.invalidateController();
560  }
561 
571  void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
572  // listen for case changes (specifically images being added, and case changes).
573  if (Case.isCaseOpen() && !listeningToAutopsy) {
576  Case.addPropertyChangeListener(caseListener);
577  listeningToAutopsy = true;
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  if (topComponent.isOpened() == false) {
703  topComponent.open();
704  }
705  topComponent.toFront();
706  /*
707  * Make this top component active so its ExplorerManager's lookup gets
708  * proxied in Utilities.actionsGlobalContext()
709  */
710  topComponent.requestActive();
711  }
712 
713  synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
714  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
715  if (currentZoom == null) {
716  advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
717  } else if (currentZoom.hasTypeZoomLevel(typeZoomeLevel) == false) {
718  advance(currentZoom.withTypeZoomLevel(typeZoomeLevel));
719  }
720  }
721 
731  synchronized public boolean pushTimeRange(Interval timeRange) {
732  //clamp timerange to case
733  Interval clampedTimeRange;
734  if (timeRange == null) {
735  clampedTimeRange = this.filteredEvents.getSpanningInterval();
736  } else {
737  Interval spanningInterval = this.filteredEvents.getSpanningInterval();
738  if (spanningInterval.overlaps(timeRange)) {
739  clampedTimeRange = spanningInterval.overlap(timeRange);
740  } else {
741  clampedTimeRange = spanningInterval;
742  }
743  }
744 
745  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
746  if (currentZoom == null) {
747  advance(InitialZoomState.withTimeRange(clampedTimeRange));
748  return true;
749  } else if (currentZoom.hasTimeRange(clampedTimeRange) == false) {
750  advance(currentZoom.withTimeRange(clampedTimeRange));
751  return true;
752  } else {
753  return false;
754  }
755  }
756 
765  synchronized public boolean pushTimeUnit(TimeUnits timeUnit) {
766  if (timeUnit == TimeUnits.FOREVER) {
767  return showFullRange();
768  } else {
769  return pushTimeRange(IntervalUtils.getIntervalAroundMiddle(filteredEvents.getTimeRange(), timeUnit.getPeriod()));
770  }
771  }
772 
773  synchronized public void pushDescrLOD(DescriptionLoD newLOD) {
774  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
775  if (currentZoom == null) {
776  advance(InitialZoomState.withDescrLOD(newLOD));
777  } else if (currentZoom.hasDescrLOD(newLOD) == false) {
778  advance(currentZoom.withDescrLOD(newLOD));
779  }
780  }
781 
782  @SuppressWarnings("AssignmentToMethodParameter") //clamp timerange to case
783  synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
784  timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
785  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
786  if (currentZoom == null) {
787  advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
788  } else if (currentZoom.hasTimeRange(timeRange) == false && currentZoom.hasTypeZoomLevel(typeZoom) == false) {
789  advance(currentZoom.withTimeAndType(timeRange, typeZoom));
790  } else if (currentZoom.hasTimeRange(timeRange) == false) {
791  advance(currentZoom.withTimeRange(timeRange));
792  } else if (currentZoom.hasTypeZoomLevel(typeZoom) == false) {
793  advance(currentZoom.withTypeZoomLevel(typeZoom));
794  }
795  }
796 
797  synchronized public void pushFilters(RootFilter filter) {
798  ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
799  if (currentZoom == null) {
800  advance(InitialZoomState.withFilter(filter.copyOf()));
801  } else if (currentZoom.hasFilter(filter) == false) {
802  advance(currentZoom.withFilter(filter.copyOf()));
803  }
804  }
805 
806  synchronized public void advance() {
807  historyManager.advance();
808  }
809 
810  synchronized public void retreat() {
811  historyManager.retreat();
812  }
813 
814  synchronized private void advance(ZoomParams newState) {
815  historyManager.advance(newState);
816  }
817 
824  synchronized public void selectEventIDs(Collection<Long> eventIDs) {
825  selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
826  selectedEventIDs.setAll(eventIDs);
827  }
828 
829  public void selectTimeAndType(Interval interval, EventType type) {
830  final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
831 
832  final LoggedTask<Collection<Long>> selectTimeAndTypeTask = new LoggedTask<Collection<Long>>("Select Time and Type", true) { //NON-NLS
833  @Override
834  protected Collection< Long> call() throws Exception {
835  synchronized (TimeLineController.this) {
836  return filteredEvents.getEventIDs(timeRange, new TypeFilter(type));
837  }
838  }
839 
840  @Override
841  protected void succeeded() {
842  super.succeeded();
843  try {
844  synchronized (TimeLineController.this) {
845  selectedTimeRange.set(timeRange);
846  selectedEventIDs.setAll(get());
847 
848  }
849  } catch (InterruptedException | ExecutionException ex) {
850  LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); //NON-NLS
851  }
852  }
853  };
854 
855  monitorTask(selectTimeAndTypeTask);
856  }
857 
864  synchronized public void monitorTask(final Task<?> task) {
865  //TODO: refactor this to use JavaFX Service? -jm
866  if (task != null) {
867  Platform.runLater(() -> {
868 
869  //is this actually threadsafe, could we get a finished task stuck in the list?
870  task.stateProperty().addListener((Observable observable) -> {
871  switch (task.getState()) {
872  case READY:
873  case RUNNING:
874  case SCHEDULED:
875  break;
876  case SUCCEEDED:
877  case CANCELLED:
878  case FAILED:
879  tasks.remove(task);
880  if (tasks.isEmpty() == false) {
881  taskProgress.bind(tasks.get(0).progressProperty());
882  taskMessage.bind(tasks.get(0).messageProperty());
883  taskTitle.bind(tasks.get(0).titleProperty());
884  }
885  break;
886  }
887  });
888  tasks.add(task);
889  taskProgress.bind(task.progressProperty());
890  taskMessage.bind(task.messageProperty());
891  taskTitle.bind(task.titleProperty());
892  switch (task.getState()) {
893  case READY:
894  executor.submit(task);
895  break;
896  case SCHEDULED:
897  case RUNNING:
898 
899  case SUCCEEDED:
900  case CANCELLED:
901  case FAILED:
902  tasks.remove(task);
903  if (tasks.isEmpty() == false) {
904  taskProgress.bind(tasks.get(0).progressProperty());
905  taskMessage.bind(tasks.get(0).messageProperty());
906  taskTitle.bind(tasks.get(0).titleProperty());
907  }
908  break;
909  }
910  });
911  }
912  }
913 
920  synchronized public void registerForEvents(Object o) {
921  eventbus.register(o);
922  }
923 
929  synchronized public void unRegisterForEvents(Object o) {
930  eventbus.unregister(o);
931  }
932 
933  static synchronized public void setTimeZone(TimeZone timeZone) {
934  TimeLineController.timeZone.set(timeZone);
935 
936  }
937 
941  @Immutable
942  private class AutopsyIngestModuleListener implements PropertyChangeListener {
943 
944  @Override
945  public void propertyChange(PropertyChangeEvent evt) {
952  try {
954  } catch (NoCurrentCaseException notUsed) {
955  // Case is closed, do nothing.
956  return;
957  }
958 
959  switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
960  case CONTENT_CHANGED:
961  case DATA_ADDED:
962  //since black board artifacts or new derived content have been added, the DB is stale.
963  Platform.runLater(() -> setEventsDBStale(true));
964  break;
965  case FILE_DONE:
966  /*
967  * Do nothing, since we have captured all new results in
968  * CONTENT_CHANGED and DATA_ADDED or the IngestJob listener,
969  */
970  break;
971  }
972  }
973  }
974 
978  @Immutable
979  private class AutopsyIngestJobListener implements PropertyChangeListener {
980 
981  @Override
982  public void propertyChange(PropertyChangeEvent evt) {
983  switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
984  case DATA_SOURCE_ANALYSIS_COMPLETED:
985  //mark db stale, and prompt to rebuild
986  Platform.runLater(() -> setEventsDBStale(true));
987  filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
988  break;
989  case DATA_SOURCE_ANALYSIS_STARTED:
990  case CANCELLED:
991  case COMPLETED:
992  case STARTED:
993  break;
994  }
995  }
996  }
997 
1001  @Immutable
1002  private class AutopsyCaseListener implements PropertyChangeListener {
1003 
1004  @Override
1005  public void propertyChange(PropertyChangeEvent evt) {
1006  switch (Case.Events.valueOf(evt.getPropertyName())) {
1007  case BLACKBOARD_ARTIFACT_TAG_ADDED:
1008  executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
1009  break;
1010  case BLACKBOARD_ARTIFACT_TAG_DELETED:
1011  executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
1012  break;
1013  case CONTENT_TAG_ADDED:
1014  executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
1015  break;
1016  case CONTENT_TAG_DELETED:
1017  executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
1018  break;
1019  case DATA_SOURCE_ADDED:
1020  //mark db stale, and prompt to rebuild
1021  Platform.runLater(() -> setEventsDBStale(true));
1022  filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
1023  break;
1024  case CURRENT_CASE:
1025  //close timeline on case changes.
1026  SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
1027  break;
1028  }
1029  }
1030  }
1031 }
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:404
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:392
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-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.