/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.timeline.ui; import com.google.common.collect.ImmutableList; import com.google.common.eventbus.Subscribe; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.concurrent.Task; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.ViewMode; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; /** * Base class for views that can be hosted in the ViewFrame * */ public abstract class AbstractTimeLineView extends BorderPane { private static final Logger LOGGER = Logger.getLogger(AbstractTimeLineView.class.getName()); /** * Boolean property that holds true if the view does not show any events * with the current zoom and filter settings. */ private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true); /** * Boolean property that holds true if the view may not represent the * current state of the DB, because, for example, tags have been updated but * the view. was not refreshed. */ private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false); /** * Listener that is attached to various properties that should trigger a * view update when they change. */ private InvalidationListener updateListener = (Observable any) -> refresh(); /** * Task used to reload the content of this view */ private Task<Boolean> updateTask; private final TimeLineController controller; private final FilteredEventsModel filteredEvents; /** * Constructor * * @param controller */ public AbstractTimeLineView(TimeLineController controller) { this.controller = controller; this.filteredEvents = controller.getEventsModel(); this.filteredEvents.registerForEvents(this); this.filteredEvents.zoomParametersProperty().addListener(updateListener); TimeLineController.getTimeZone().addListener(updateListener); } /** * Handle a RefreshRequestedEvent from the events model by updating the * view. * * @param event The RefreshRequestedEvent to handle. */ @Subscribe public void handleRefreshRequested(RefreshRequestedEvent event) { refresh(); } /** * Does the view represent an out-of-date state of the DB. It might if, for * example, tags have been updated but the view was not refreshed. * * @return True if the view does not represent the current state of the DB. */ public boolean isOutOfDate() { return outOfDate.get(); } /** * Get a ReadOnlyBooleanProperty that holds true if this view does not * represent the current state of the DB> * * @return A ReadOnlyBooleanProperty that holds the out-of-date state for * this view. */ public ReadOnlyBooleanProperty outOfDateProperty() { return outOfDate.getReadOnlyProperty(); } /** * Get the TimelineController for this view. * * @return The TimelineController for this view. */ protected TimeLineController getController() { return controller; } /** * Refresh this view based on current state of zoom / filters. Primarily * this invokes the background ViewRefreshTask returned by getUpdateTask(), * which derived classes must implement. * * TODO: replace this logic with a javafx Service ? -jm */ protected final synchronized void refresh() { if (updateTask != null) { updateTask.cancel(true); updateTask = null; } updateTask = getNewUpdateTask(); updateTask.stateProperty().addListener((Observable observable) -> { switch (updateTask.getState()) { case CANCELLED: case FAILED: case READY: case RUNNING: case SCHEDULED: break; case SUCCEEDED: try { this.hasVisibleEvents.set(updateTask.get()); } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception updating view", ex); //NON-NLS } break; } }); getController().monitorTask(updateTask); } /** * Get the FilteredEventsModel for this view. * * @return The FilteredEventsModel for this view. */ protected FilteredEventsModel getEventsModel() { return filteredEvents; } /** * Get a new background Task that fetches the appropriate data and loads it * into this view. * * @return A new task to execute on a background thread to reload this view * with different data. */ protected abstract Task<Boolean> getNewUpdateTask(); /** * Get the ViewMode for this view. * * @return The ViewMode for this view. */ protected abstract ViewMode getViewMode(); /** * Get a List of Nodes containing settings widgets to insert into top * ToolBar of the ViewFrame. * * @return The List of settings Nodes. */ abstract protected ImmutableList<Node> getSettingsControls(); /** * Does this view have custom time navigation controls that should replace * the default ones from the ViewFrame? * * @return True if this view have custom time navigation controls. */ abstract protected boolean hasCustomTimeNavigationControls(); /** * Get a List of Nodes containing controls to insert into the lower time * range ToolBar of the ViewFrame. * * @return The List of Nodes. */ abstract protected ImmutableList<Node> getTimeNavigationControls(); /** * Dispose of this view and any resources it holds onto. */ final synchronized void dispose() { //cancel and gc updateTask if (updateTask != null) { updateTask.cancel(true); updateTask = null; } //remvoe and gc updateListener this.filteredEvents.zoomParametersProperty().removeListener(updateListener); TimeLineController.getTimeZone().removeListener(updateListener); updateListener = null; filteredEvents.unRegisterForEvents(this); controller.unRegisterForEvents(this); } /** * Are there are any events visible in this view with the current view * parameters? * * @return True if there are events visible in this view with the current * view parameters. */ boolean hasVisibleEvents() { return hasVisibleEventsProperty().get(); } /** * A property that indicates whether there are any events visible in this * view with the current view parameters. * * @return A property that indicates whether there are any events visible in * this view with the current view parameters. */ ReadOnlyBooleanProperty hasVisibleEventsProperty() { return hasVisibleEvents.getReadOnlyProperty(); } /** * Set this view out of date because, for example, tags have been updated * but the view was not refreshed. */ void setOutOfDate() { outOfDate.set(true); } /** * Clear all data items from this chart. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) abstract protected void clearData(); /** * Base class for Tasks that refreshes a view when the view settings change. * * @param <AxisValuesType> The type of a single object that can represent * the range of data displayed along the X-Axis. */ protected abstract class ViewRefreshTask<AxisValuesType> extends LoggedTask<Boolean> { private final Node center; /** * Constructor * * @param taskName The name of this task. * @param logStateChanges Whether or not task state changes should be * logged. */ protected ViewRefreshTask(String taskName, boolean logStateChanges) { super(taskName, logStateChanges); this.center = getCenter(); } /** * Sets initial progress value and message and shows blocking progress * indicator over the view. Derived Tasks should be sure to call this as * part of their call() implementation. * * @return True * * @throws Exception If there is an unhandled exception during the * background operation */ @NbBundle.Messages(value = {"ViewRefreshTask.preparing=Analyzing zoom and filter settings"}) @Override protected Boolean call() throws Exception { updateProgress(-1, 1); updateMessage(Bundle.ViewRefreshTask_preparing()); Platform.runLater(() -> { MaskerPane maskerPane = new MaskerPane(); maskerPane.textProperty().bind(messageProperty()); maskerPane.progressProperty().bind(progressProperty()); setCenter(new StackPane(center, maskerPane)); setCursor(Cursor.WAIT); }); return true; } /** * Updates the horizontal axis and removes the blocking progress * indicator. Derived Tasks should be sure to call this as part of their * succeeded() implementation. */ @Override protected void succeeded() { super.succeeded(); outOfDate.set(false); cleanup(); } /** * Removes the blocking progress indicator. Derived Tasks should be sure * to call this as part of their cancelled() implementation. */ @Override protected void cancelled() { super.cancelled(); cleanup(); } /** * Removes the blocking progress indicator. Derived Tasks should be sure * to call this as part of their failed() implementation. */ @Override protected void failed() { super.failed(); cleanup(); } /** * Removes the blocking progress indicator and reset the cursor to the * default. */ private void cleanup() { setCenter(center); //clear masker pane installed in call() setCursor(Cursor.DEFAULT); } /** * Set the horizontal range that this chart will show. * * @param values A single object representing the range that this chart * will show. */ protected abstract void setDateValues(AxisValuesType values); /** * Clears the chart data and sets the horizontal axis range. For use * within the derived implementation of the call() method. * * @param axisValues */ @ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI) protected void resetView(AxisValuesType axisValues) { Platform.runLater(() -> { clearData(); setDateValues(axisValues); }); } } }