From 329100ec80827a19ce5c21d1a51fe16d8d42fab2 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 16 Nov 2015 17:14:51 -0500 Subject: [PATCH 1/5] transition prompt dialogs to JFX and new dialog manager. changing to service has caused multipe progess dialogs... --- .../autopsy/timeline/PromptDialogManager.java | 181 ++++++++++++ .../autopsy/timeline/TimeLineController.java | 261 ++++++------------ .../autopsy/timeline/db/EventsRepository.java | 13 +- 3 files changed, 264 insertions(+), 191 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java new file mode 100644 index 0000000000..538420e31e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -0,0 +1,181 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline; + +import java.io.IOException; +import java.net.URL; +import java.util.logging.Level; +import javafx.concurrent.Worker; +import javafx.event.EventHandler; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogEvent; +import javafx.scene.control.DialogPane; +import javafx.scene.image.Image; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import org.controlsfx.dialog.ProgressDialog; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; + +/** + * + */ +public class PromptDialogManager { + + private static final Logger LOGGER = Logger.getLogger(PromptDialogManager.class.getName()); + private static final Image LOGO; + + static { + Image x = null; + try { + x = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Failed to laod branded icon for progress dialog.", ex); + } + LOGO = x; + } + private Dialog currentDialog; + + private final TimeLineController controller; + + PromptDialogManager(TimeLineController controller) { + this.controller = controller; + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + @NbBundle.Messages({"Timeline.progressWindow.title=Populating Timeline Data"}) + void showProgressDialog(Worker task) { + currentDialog = new ProgressDialog(task); + currentDialog.setTitle(Bundle.Timeline_progressWindow_title()); + DialogPane dialogPane = currentDialog.getDialogPane(); + dialogPane.getButtonTypes().add(ButtonType.CANCEL); + Stage dialogStage = (Stage) dialogPane.getScene().getWindow(); + dialogPane.setPrefWidth(400); + currentDialog.headerTextProperty().bind(task.titleProperty()); + dialogStage.getIcons().setAll(LOGO); + currentDialog.setOnCloseRequest(closeRequestEvent -> task.cancel()); + currentDialog.setOnHidden(new EventHandler() { + + @Override + public void handle(DialogEvent event) { + if (currentDialog == progressDialog) { + currentDialog = null; + } + } + }); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + boolean showConfirmationDialog(String title, String headerText, String contentText, ButtonType okButton, ButtonType cancelButton) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, contentText, okButton, cancelButton); + alert.initStyle(StageStyle.UTILITY); + alert.initModality(Modality.APPLICATION_MODAL); +// alert.initOwner(mainFrame); + + alert.setHeaderText(headerText); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.setTitle(title); +// alert = alert; + return alert.showAndWait().map(okButton::equals).orElse(false); + } + + /** + * prompt the user to rebuild the db because that datasource_ids are missing + * from the database and that the datasource filter will not work + * + * @return true if they agree to rebuild + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + @NbBundle.Messages({"datasource.missing.header=The Timeline events database was previously populated without datasource information." + + "\nThe data source filter will be unavailable unless you update the events database." + }) + synchronized boolean confirmDataSourceIDsMissingRebuild() { + return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), + Bundle.datasource_missing_header(), + Bundle.TimeLinecontroller_updateNowQuestion(), + new ButtonType("Update", ButtonBar.ButtonData.OK_DONE), + new ButtonType("Continue without updating", ButtonBar.ButtonData.CANCEL_CLOSE)); + } + + /** + * prompt the user to rebuild the db because the db was last build during + * ingest and may be incomplete + * + * @return true if they agree to rebuild + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + @NbBundle.Messages({"Timeline.do_repopulate.msg=The Timeline events database was previously populated while ingest was running." + + "\nSome events may not have been populated or may have been populated inaccurately." + }) + synchronized boolean confirmLastBuiltDuringIngestRebuild() { + return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), + Bundle.Timeline_do_repopulate_msg(), + Bundle.TimeLinecontroller_updateNowQuestion(), + new ButtonType("Update", ButtonBar.ButtonData.OK_DONE), + new ButtonType("Continue without updating", ButtonBar.ButtonData.CANCEL_CLOSE)); +// return JOptionPane.showConfirmDialog(mainFrame, +// Bundle.Timeline_do_repopulate_msg(), +// Bundle.Timeline_confirmation_dialogs_title(), +// JOptionPane.YES_NO_OPTION, +// JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION; + } + + /** + * prompt the user to rebuild the db because the db is out of date and + * doesn't include things from subsequent ingests + * + * @return true if they agree to rebuild + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + @NbBundle.Messages({"Timeline.propChg.confDlg.timelineOOD.msg=The event data is out of date.",}) + synchronized boolean confirmOutOfDateRebuild() { + return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), + Bundle.Timeline_propChg_confDlg_timelineOOD_msg(), + Bundle.TimeLinecontroller_updateNowQuestion(), + new ButtonType("Update", ButtonBar.ButtonData.OK_DONE), + new ButtonType("Continue without updating", ButtonBar.ButtonData.CANCEL_CLOSE)); +// return JOptionPane.showConfirmDialog(mainFrame, +// Bundle.Timeline_propChg_confDlg_timelineOOD_msg(), +// Bundle.Timeline_confirmation_dialogs_title(), +// JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; + } + + /** + * prompt the user that ingest is running and the db may not end up + * complete. + * + * @return true if they want to continue anyways + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + @NbBundle.Messages({"Timeline.initTimeline.confDlg.genBeforeIngest.msg=You are trying to generate a timeline before ingest has been completed. " + + "The timeline may be incomplete.", + "Timeline.initTimeline.confDlg.genBeforeIngest.question=Do you want to continue?"}) + synchronized boolean confirmRebuildDuringIngest() { + return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), + Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(), + Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_question(), + new ButtonType("Show Timeline", ButtonBar.ButtonData.OK_DONE), + new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE)); +// return JOptionPane.showConfirmDialog(mainFrame, +// Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(), +// Bundle.Timeline_confirmation_dialogs_title(), +// JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + boolean bringCurrentDialogToFront() { + if (currentDialog != null && currentDialog.isShowing()) { + ((Stage) currentDialog.getDialogPane().getScene().getWindow()).toFront(); + return true; + } + return false; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 4a04a4a630..83fec19c6e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -21,8 +21,6 @@ package org.sleuthkit.autopsy.timeline; import java.awt.HeadlessException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.IOException; -import java.net.URL; import java.sql.ResultSet; import java.sql.SQLException; import java.text.NumberFormat; @@ -52,15 +50,11 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.concurrent.Worker; -import javafx.scene.control.ButtonType; -import javafx.scene.control.DialogPane; -import javafx.scene.image.Image; -import javafx.stage.Stage; +import javafx.scene.control.Dialog; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.controlsfx.dialog.ProgressDialog; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; @@ -110,7 +104,8 @@ import org.sleuthkit.datamodel.TskCoreException; * *
    */ -@NbBundle.Messages({"Timeline.confirmation.dialogs.title=Update Timeline database?"}) +@NbBundle.Messages({"Timeline.confirmation.dialogs.title=Update Timeline database?", + "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"}) public class TimeLineController { private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName()); @@ -146,7 +141,7 @@ public class TimeLineController { private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper(); @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private ProgressDialog taskProgressDialog; + private Dialog currentDialog; /** * status is a string that will be displayed in the status bar as a kind of @@ -266,6 +261,8 @@ public class TimeLineController { } private final ReadOnlyBooleanWrapper newEventsFlag = new ReadOnlyBooleanWrapper(false); + private final PromptDialogManager promptDialogManager = new PromptDialogManager(this); + public TimeLineController(Case autoCase) { this.autoCase = autoCase; @@ -316,34 +313,32 @@ public class TimeLineController { * the user aborted after prompt about ingest running. True if the * repo was rebuilt. */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean rebuildRepo() { if (IngestManager.getInstance().isIngestRunning()) { //confirm timeline during ingest - if (confirmRebuildDuringIngest() == false) { + if (promptDialogManager.confirmRebuildDuringIngest() == false) { return false; } } SwingUtilities.invokeLater(this::closeTimelineWindow); - Platform.runLater(() -> { - final Worker rebuildRepository = eventsRepository.rebuildRepository(); - rebuildRepository.stateProperty().addListener((stateProperty, oldState, newSate) -> { - //this will be on JFX thread - if (newSate == Worker.State.SUCCEEDED) { - //TODO: this looks hacky. what is going on? should this be an event? - needsHistogramRebuild.set(true); - needsHistogramRebuild.set(false); - SwingUtilities.invokeLater(TimeLineController.this::showWindow); + final Worker rebuildRepository = eventsRepository.rebuildRepository(); + rebuildRepository.stateProperty().addListener((stateProperty, oldState, newSate) -> { + //this will be on JFX thread + if (newSate == Worker.State.SUCCEEDED) { + //TODO: this looks hacky. what is going on? should this be an event? + needsHistogramRebuild.set(true); + needsHistogramRebuild.set(false); + SwingUtilities.invokeLater(TimeLineController.this::showWindow); - //TODO: should this be an event? - newEventsFlag.set(false); - historyManager.reset(filteredEvents.zoomParametersProperty().get()); - TimeLineController.this.showFullRange(); + //TODO: should this be an event? + newEventsFlag.set(false); + historyManager.reset(filteredEvents.zoomParametersProperty().get()); + TimeLineController.this.showFullRange(); - } - }); - showProgressDialog(rebuildRepository); + } }); + promptDialogManager.showProgressDialog(rebuildRepository); return true; } @@ -351,21 +346,21 @@ public class TimeLineController { * Since tags might have changed while TimeLine wasn't listening, drop the * tags table and rebuild it by querying for all the tags and inserting them * in to the TimeLine DB. + * */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) void rebuildTagsTable() { SwingUtilities.invokeLater(this::closeTimelineWindow); - Platform.runLater(() -> { - Worker rebuildTags = eventsRepository.rebuildTags(); - rebuildTags.stateProperty().addListener((stateProperty, oldState, newSate) -> { - //this will be on JFX thread - if (newSate == Worker.State.SUCCEEDED) { - SwingUtilities.invokeLater(TimeLineController.this::showWindow); - showFullRange(); - } - }); - showProgressDialog(rebuildTags); + Worker rebuildTags = eventsRepository.rebuildTags(); + rebuildTags.stateProperty().addListener((stateProperty, oldState, newSate) -> { + //this will be on JFX thread + if (newSate == Worker.State.SUCCEEDED) { + SwingUtilities.invokeLater(TimeLineController.this::showWindow); + showFullRange(); + } }); + promptDialogManager.showProgressDialog(rebuildTags); } @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -375,32 +370,6 @@ public class TimeLineController { } } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"Timeline.progressWindow.title=Populating Timeline Data"}) - private void showProgressDialog(Worker task) { - taskProgressDialog = new ProgressDialog(task); - taskProgressDialog.setTitle(Bundle.Timeline_progressWindow_title()); - DialogPane dialogPane = taskProgressDialog.getDialogPane(); - dialogPane.getButtonTypes().add(ButtonType.CANCEL); - Stage dialogStage = (Stage) dialogPane.getScene().getWindow(); - dialogPane.setPrefWidth(400); - taskProgressDialog.headerTextProperty().bind(task.titleProperty()); - dialogStage.getIcons().setAll(LOGO); - taskProgressDialog.setOnCloseRequest(closeRequestEvent -> task.cancel()); - } - - private static final Image LOGO; - - static { - Image x = null; - try { - x = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to laod branded icon for progress dialog.", ex); - } - LOGO = x; - } - public void showFullRange() { synchronized (filteredEvents) { pushTimeRange(filteredEvents.getSpanningInterval()); @@ -432,66 +401,63 @@ public class TimeLineController { listeningToAutopsy = true; } - try { - if (eventsRepository.isRebuilding()) { - Platform.runLater(() -> { - if (taskProgressDialog != null) { - ((Stage) taskProgressDialog.getDialogPane().getScene().getWindow()).toFront(); - } - }); - return; - } + Platform.runLater(() -> { + try { + if (promptDialogManager.bringCurrentDialogToFront()) { + return; + } - boolean repoRebuilt = false; //has the repo been rebuilt - long timeLineLastObjectId = eventsRepository.getLastObjID(); + boolean repoRebuilt = false; //has the repo been rebuilt + long timeLineLastObjectId = eventsRepository.getLastObjID(); - //if the repo is empty rebuild it - if (timeLineLastObjectId == -1) { - repoRebuilt = rebuildRepo(); - } + //if the repo is empty rebuild it + if (timeLineLastObjectId == -1) { + repoRebuilt = rebuildRepo(); + } - if (repoRebuilt == false) { - //if ingest was running uring last rebuild, prompt to rebuild - if (eventsRepository.getWasIngestRunning()) { - if (confirmLastBuiltDuringIngestRebuild()) { - repoRebuilt = rebuildRepo(); + if (repoRebuilt == false) { + //if ingest was running uring last rebuild, prompt to rebuild + if (eventsRepository.getWasIngestRunning()) { + if (promptDialogManager.confirmLastBuiltDuringIngestRebuild()) { + repoRebuilt = rebuildRepo(); + } } } - } - if (repoRebuilt == false) { - final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase(); - //if the last artifact and object ids don't match between skc and tldb, prompt to rebuild - if (sleuthkitCase.getLastObjectId() != timeLineLastObjectId - || getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) { - if (confirmOutOfDateRebuild()) { - repoRebuilt = rebuildRepo(); + if (repoRebuilt == false) { + final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase(); + //if the last artifact and object ids don't match between skc and tldb, prompt to rebuild + if (sleuthkitCase.getLastObjectId() != timeLineLastObjectId + || getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) { + if (promptDialogManager.confirmOutOfDateRebuild()) { + repoRebuilt = rebuildRepo(); + } } } - } - if (repoRebuilt == false) { - // if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild - if (eventsRepository.hasNewColumns() == false) { - if (confirmDataSourceIDsMissingRebuild()) { - repoRebuilt = rebuildRepo(); + if (repoRebuilt == false) { + // if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild + if (eventsRepository.hasNewColumns() == false) { + if (promptDialogManager.confirmDataSourceIDsMissingRebuild()) { + repoRebuilt = rebuildRepo(); + } } } - } - /* - * if the repo was not rebuilt at minimum rebuild the tags which may - * have been updated without our knowing it. - */ - if (repoRebuilt == false) { - rebuildTagsTable(); - } + /* + * if the repo was not rebuilt at minimum rebuild the tags which + * may have been updated without our knowing it. + */ + if (repoRebuilt == false) { + rebuildTagsTable(); + } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error when generating timeline, ", ex); // NON-NLS - } catch (HeadlessException | MissingResourceException ex) { - LOGGER.log(Level.SEVERE, "Unexpected error when generating timeline, ", ex); // NON-NLS - } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error when generating timeline, ", ex); // NON-NLS + } catch (HeadlessException | MissingResourceException ex) { + LOGGER.log(Level.SEVERE, "Unexpected error when generating timeline, ", ex); // NON-NLS + } + }); } public static long getCaseLastArtifactID(final SleuthkitCase sleuthkitCase) { @@ -788,79 +754,14 @@ public class TimeLineController { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void confirmOutOfDateRebuildIfWindowOpen() throws MissingResourceException, HeadlessException { if (isWindowOpen()) { - if (confirmOutOfDateRebuild()) { - rebuildRepo(); - } + Platform.runLater(() -> { + if (promptDialogManager.confirmOutOfDateRebuild()) { + rebuildRepo(); + } + }); } } - /** - * prompt the user to rebuild the db because that datasource_ids are missing - * from the database and that the datasource filter will not work - * - * @return true if they agree to rebuild - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - @NbBundle.Messages({"datasource.missing.confirmation=The Timeline events database was previously populated with an old version of Autopsy." - + "\nThe data source filter will be unavailable unless you update the events database." - + "\nDo you want to update the events database now?"}) - synchronized boolean confirmDataSourceIDsMissingRebuild() { - return JOptionPane.showConfirmDialog(mainFrame, - Bundle.datasource_missing_confirmation(), - Bundle.Timeline_confirmation_dialogs_title(), - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION; - } - - /** - * prompt the user to rebuild the db because the db was last build during - * ingest and may be incomplete - * - * @return true if they agree to rebuild - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - @NbBundle.Messages({"Timeline.do_repopulate.msg=The Timeline events database was previously populated while ingest was running." - + "\nSome events may not have been populated or may have been populated inaccurately." - + "\nDo you want to repopulate the events database now?"}) - synchronized boolean confirmLastBuiltDuringIngestRebuild() { - return JOptionPane.showConfirmDialog(mainFrame, - Bundle.Timeline_do_repopulate_msg(), - Bundle.Timeline_confirmation_dialogs_title(), - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION; - } - - /** - * prompt the user to rebuild the db because the db is out of date and - * doesn't include things from subsequent ingests - * - * @return true if they agree to rebuild - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - @NbBundle.Messages({"Timeline.propChg.confDlg.timelineOOD.msg=The event data is out of date. Would you like to regenerate it?",}) - synchronized boolean confirmOutOfDateRebuild() throws MissingResourceException, HeadlessException { - return JOptionPane.showConfirmDialog(mainFrame, - Bundle.Timeline_propChg_confDlg_timelineOOD_msg(), - Bundle.Timeline_confirmation_dialogs_title(), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; - } - - /** - * prompt the user that ingest is running and the db may not end up - * complete. - * - * @return true if they want to continue anyways - */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - @NbBundle.Messages({"Timeline.initTimeline.confDlg.genBeforeIngest.msg=You are trying to generate a timeline before ingest has been completed. " - + "The timeline may be incomplete. Do you want to continue?"}) - synchronized boolean confirmRebuildDuringIngest() throws MissingResourceException, HeadlessException { - return JOptionPane.showConfirmDialog(mainFrame, - Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(), - Bundle.Timeline_confirmation_dialogs_title(), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; - } - private class AutopsyIngestModuleListener implements PropertyChangeListener { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index d2b6046aa2..3542120824 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -31,14 +31,11 @@ import java.util.Map; import static java.util.Objects.isNull; import java.util.Set; import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -403,15 +400,9 @@ public class EventsRepository { recordWasIngestRunning(injestRunning); } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public boolean isRebuilding() { - FutureTask task = new FutureTask<>(dbPopulationService::isRunning); - Platform.runLater(task); - try { - return task.get(); - } catch (InterruptedException | ExecutionException exception) { - LOGGER.log(Level.SEVERE, "There was an error determining the state of the db population service.", exception); - } - return false; + return dbPopulationService.isRunning(); } /** From a8613e6aa19c44b27d0431f7c4332442147e8b38 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 17 Nov 2015 16:32:57 -0500 Subject: [PATCH 2/5] backout change to service. consolidate tasks under concrete implementation of new abstract class. improve cancellation progress display --- .../timeline/CancellationProgressTask.java | 36 ++ .../autopsy/timeline/OpenTimelineAction.java | 14 +- .../autopsy/timeline/PromptDialogManager.java | 52 ++- .../autopsy/timeline/TimeLineController.java | 4 +- .../datamodel/FilteredEventsModel.java | 4 +- .../autopsy/timeline/db/EventDB.java | 6 +- .../autopsy/timeline/db/EventsRepository.java | 404 ++++++++---------- 7 files changed, 255 insertions(+), 265 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/CancellationProgressTask.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/CancellationProgressTask.java b/Core/src/org/sleuthkit/autopsy/timeline/CancellationProgressTask.java new file mode 100644 index 0000000000..d6679531ca --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/CancellationProgressTask.java @@ -0,0 +1,36 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline; + +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.concurrent.Task; + +/** + * An extension of Task that allows a client to request cancellation, with out + * the Task entering the cancelled state immediately. This allows the task to + * continue to report progress of eg its cleanup operations. Implementations + * should use the {@link #isCancelRequested() } method to check for cancelation + * and call cancel() before returning form the call() method. + */ +public abstract class CancellationProgressTask extends Task { + + private boolean cancelRequested; + + public synchronized boolean isCancelRequested() { + return cancelRequested || isCancelled(); + } + + public synchronized boolean requestCancel() { + this.cancelRequested = true; + return true; + } + + abstract public ReadOnlyBooleanProperty cancellableProperty(); + + boolean isCancellable() { + return cancellableProperty().get(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index ad5ffe8323..78b21bd9e8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -73,12 +73,14 @@ public class OpenTimelineAction extends CallableSystemAction { LOGGER.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS return; } - if (timeLineController == null) { - timeLineController = new TimeLineController(currentCase); - } else if (timeLineController.getAutopsyCase() != currentCase) { - timeLineController.closeTimeLine(); - timeLineController = new TimeLineController(currentCase); - } + + if (timeLineController == null) { + timeLineController = new TimeLineController(currentCase); + } else if (timeLineController.getAutopsyCase() != currentCase) { + timeLineController.closeTimeLine(); + timeLineController = new TimeLineController(currentCase); + } + timeLineController.openTimeLine(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java index 538420e31e..a864ab2687 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -8,13 +8,11 @@ package org.sleuthkit.autopsy.timeline; import java.io.IOException; import java.net.URL; import java.util.logging.Level; -import javafx.concurrent.Worker; -import javafx.event.EventHandler; +import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Dialog; -import javafx.scene.control.DialogEvent; import javafx.scene.control.DialogPane; import javafx.scene.image.Image; import javafx.stage.Modality; @@ -52,39 +50,49 @@ public class PromptDialogManager { @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @NbBundle.Messages({"Timeline.progressWindow.title=Populating Timeline Data"}) - void showProgressDialog(Worker task) { + public void showProgressDialog(CancellationProgressTask task) { + currentDialog = new ProgressDialog(task); currentDialog.setTitle(Bundle.Timeline_progressWindow_title()); - DialogPane dialogPane = currentDialog.getDialogPane(); - dialogPane.getButtonTypes().add(ButtonType.CANCEL); - Stage dialogStage = (Stage) dialogPane.getScene().getWindow(); - dialogPane.setPrefWidth(400); currentDialog.headerTextProperty().bind(task.titleProperty()); - dialogStage.getIcons().setAll(LOGO); - currentDialog.setOnCloseRequest(closeRequestEvent -> task.cancel()); - currentDialog.setOnHidden(new EventHandler() { - @Override - public void handle(DialogEvent event) { - if (currentDialog == progressDialog) { - currentDialog = null; - } + DialogPane dialogPane = currentDialog.getDialogPane(); + dialogPane.setPrefWidth(400); + + dialogPane.setPrefHeight(200); + task.setOnCancelled(cancelled -> currentDialog.close()); + task.setOnSucceeded(succeeded -> currentDialog.close()); + + dialogPane.getButtonTypes().setAll(ButtonType.CANCEL); + final Node cancelButton = dialogPane.lookupButton(ButtonType.CANCEL); + cancelButton.disableProperty().bind(task.cancellableProperty().not()); + currentDialog.setOnCloseRequest(closeRequest -> { + + if (task.isRunning()) { + closeRequest.consume(); + } + if (task.isCancellable() && task.isCancelRequested() == false) { + task.requestCancel(); } }); + + Stage stage = (Stage) dialogPane.getScene().getWindow(); + stage.getIcons().setAll(LOGO); + currentDialog.show(); } @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean showConfirmationDialog(String title, String headerText, String contentText, ButtonType okButton, ButtonType cancelButton) { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION, contentText, okButton, cancelButton); - alert.initStyle(StageStyle.UTILITY); - alert.initModality(Modality.APPLICATION_MODAL); + currentDialog = new Alert(Alert.AlertType.CONFIRMATION, contentText, okButton, cancelButton); + currentDialog.initStyle(StageStyle.UTILITY); + currentDialog.initModality(Modality.APPLICATION_MODAL); // alert.initOwner(mainFrame); - alert.setHeaderText(headerText); - Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + currentDialog.setHeaderText(headerText); + Stage stage = (Stage) currentDialog.getDialogPane().getScene().getWindow(); stage.setTitle(title); // alert = alert; - return alert.showAndWait().map(okButton::equals).orElse(false); + return currentDialog.showAndWait().map(okButton::equals).orElse(false); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 83fec19c6e..294f24c339 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -322,7 +322,7 @@ public class TimeLineController { } } SwingUtilities.invokeLater(this::closeTimelineWindow); - final Worker rebuildRepository = eventsRepository.rebuildRepository(); + final CancellationProgressTask rebuildRepository = eventsRepository.rebuildRepository(); rebuildRepository.stateProperty().addListener((stateProperty, oldState, newSate) -> { //this will be on JFX thread if (newSate == Worker.State.SUCCEEDED) { @@ -352,7 +352,7 @@ public class TimeLineController { void rebuildTagsTable() { SwingUtilities.invokeLater(this::closeTimelineWindow); - Worker rebuildTags = eventsRepository.rebuildTags(); + CancellationProgressTask rebuildTags = eventsRepository.rebuildTags(); rebuildTags.stateProperty().addListener((stateProperty, oldState, newSate) -> { //this will be on JFX thread if (newSate == Worker.State.SUCCEEDED) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 499b42a6be..c11bb97012 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -355,14 +355,14 @@ public final class FilteredEventsModel { synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) { ContentTag contentTag = evt.getAddedTag(); Content content = contentTag.getContent(); - Set updatedEventIDs = repo.addTag(content.getId(), null, contentTag); + Set updatedEventIDs = repo.addTag(content.getId(), null, contentTag, null); return postTagsUpdated(updatedEventIDs); } synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) { BlackboardArtifactTag artifactTag = evt.getAddedTag(); BlackboardArtifact artifact = artifactTag.getArtifact(); - Set updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag);; + Set updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag, null); return postTagsUpdated(updatedEventIDs); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index d2b035bd37..1c1d3e3ab7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -810,7 +810,10 @@ public class EventDB { * * @return the event ids that match the object/artifact pair */ - Set addTag(long objectID, @Nullable Long artifactID, Tag tag) { + Set addTag(long objectID, @Nullable Long artifactID, Tag tag, EventTransaction transaction) { + if (transaction != null && transaction.isClosed()) { + throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS + } DBLock.lock(); try { Set eventIDs = markEventsTagged(objectID, artifactID, true); @@ -1265,7 +1268,6 @@ public class EventDB { DBLock.lock(); try { con.setAutoCommit(false); - } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "failed to set auto-commit to to false", ex); // NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 3542120824..cfb4a0eb42 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -36,13 +36,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; +import javafx.application.Platform; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; -import javafx.concurrent.Service; -import javafx.concurrent.Task; -import javafx.concurrent.Worker; import javax.swing.JOptionPane; import org.apache.commons.lang3.StringUtils; import org.joda.time.Interval; @@ -54,6 +54,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.timeline.CancellationProgressTask; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; @@ -95,16 +96,14 @@ public class EventsRepository { private final static Logger LOGGER = Logger.getLogger(EventsRepository.class.getName()); + private final Executor workerExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("eventrepository-worker-%d").build()); + private DBPopulationWorker dbWorker; private final EventDB eventDB; - - private final DBPopulationService dbPopulationService = new DBPopulationService(this); - - private final LoadingCache maxCache; - - private final LoadingCache minCache; - + private final Case autoCase; private final FilteredEventsModel modelInstance; + private final LoadingCache maxCache; + private final LoadingCache minCache; private final LoadingCache idToEventCache; private final LoadingCache> eventCountsCache; private final LoadingCache> eventClusterCache; @@ -112,7 +111,6 @@ public class EventsRepository { private final ObservableMap datasourcesMap = FXCollections.observableHashMap(); private final ObservableMap hashSetMap = FXCollections.observableHashMap(); private final ObservableList tagNames = FXCollections.observableArrayList(); - private final Case autoCase; public Case getAutoCase() { return autoCase; @@ -283,8 +281,8 @@ public class EventsRepository { } } - synchronized public Set addTag(long objID, Long artifactID, Tag tag) { - Set updatedEventIDs = eventDB.addTag(objID, artifactID, tag); + synchronized public Set addTag(long objID, Long artifactID, Tag tag, EventDB.EventTransaction trans) { + Set updatedEventIDs = eventDB.addTag(objID, artifactID, tag, trans); if (!updatedEventIDs.isEmpty()) { invalidateCaches(updatedEventIDs); } @@ -327,67 +325,6 @@ public class EventsRepository { } } - static private final Executor workerExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("eventrepository-worker-%d").build()); - - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - public Worker rebuildRepository() { - return rebuildRepository(DBPopulationService.DBPopulationMode.FULL); - } - - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - public Worker rebuildTags() { - return rebuildRepository(DBPopulationService.DBPopulationMode.TAGS_ONLY); - } - - /** - * - * @param mode the value of mode - */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private Worker rebuildRepository(final DBPopulationService.DBPopulationMode mode) { - LOGGER.log(Level.INFO, "(re) starting {0}db population task", mode); - dbPopulationService.setDBPopulationMode(mode); - dbPopulationService.restart(); - return dbPopulationService; - } - - private static class DBPopulationService extends Service { - - enum DBPopulationMode { - - FULL, - TAGS_ONLY; - } - - private final EventsRepository eventRepo; - - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private DBPopulationMode dbPopulationMode = DBPopulationMode.FULL; - - DBPopulationService(EventsRepository eventRepo) { - this.eventRepo = eventRepo; - setExecutor(workerExecutor); - } - - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - public final void setDBPopulationMode(DBPopulationMode value) { - dbPopulationMode = value; - } - - @Override - protected Task createTask() { - DBPopulationMode dbPopMode = dbPopulationMode; - switch (dbPopMode) { - case FULL: - return eventRepo.new DBPopulationWorker(); - case TAGS_ONLY: - return eventRepo.new RebuildTagsWorker(); - default: - throw new IllegalArgumentException("Unknown db population mode: " + dbPopMode + ". Skipping db population."); - } - } - } - /** * * @param lastObjId the value of lastObjId @@ -400,29 +337,69 @@ public class EventsRepository { recordWasIngestRunning(injestRunning); } + public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) { + return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2)); + } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public boolean isRebuilding() { - return dbPopulationService.isRunning(); + return dbWorker.isRunning(); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + public CancellationProgressTask rebuildRepository() { + return rebuildRepository(DBPopulationMode.FULL); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + public CancellationProgressTask rebuildTags() { + return rebuildRepository(DBPopulationMode.TAGS_ONLY); } /** - * A base class for Tasks that show a updates a {@link ProgressHandle} as it - * performs its background work on the events DB. * - * //TODO: I don't like the coupling to ProgressHandle, but the - * alternatives I can think of seem even worse. -jm + * @param mode the value of mode */ - private abstract class DBProgressWorker extends Task { + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private CancellationProgressTask rebuildRepository(final DBPopulationMode mode) { + LOGGER.log(Level.INFO, "(re)starting {0} db population task", mode); + if (dbWorker != null) { + dbWorker.cancel(); + } + dbWorker = new DBPopulationWorker(mode); + workerExecutor.execute(dbWorker); + return dbWorker; + } - final SleuthkitCase skCase; - final TagsManager tagsManager; + private enum DBPopulationMode { - volatile ProgressHandle progressHandle; + FULL, + TAGS_ONLY; + } - DBProgressWorker(String initialProgressDisplayName) { - progressHandle = ProgressHandleFactory.createHandle(initialProgressDisplayName, this::cancel); - skCase = autoCase.getSleuthkitCase(); - tagsManager = autoCase.getServices().getTagsManager(); + /** + * //TODO: I don't like the coupling to ProgressHandle in this task, but + * the alternatives I can think of seem even worse. -jm + */ + private class DBPopulationWorker extends CancellationProgressTask { + + private final ReadOnlyBooleanWrapper cancellable = new ReadOnlyBooleanWrapper(true); + + private final DBPopulationMode dbPopulationMode; + private final SleuthkitCase skCase; + private final TagsManager tagsManager; + + private ProgressHandle progressHandle; + + @Override + public ReadOnlyBooleanProperty cancellableProperty() { + return cancellable.getReadOnlyProperty(); + } + + @Override + public boolean requestCancel() { + Platform.runLater(() -> cancellable.set(false)); + return super.requestCancel(); } @Override @@ -453,157 +430,125 @@ public class EventsRepository { progressHandle.progress((int) workDone); } } - } - public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) { - return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2)); - } + DBPopulationWorker(DBPopulationMode mode) { + skCase = autoCase.getSleuthkitCase(); + tagsManager = autoCase.getServices().getTagsManager(); + this.dbPopulationMode = mode; + } - private class RebuildTagsWorker extends DBProgressWorker { + void restartProgressHandle(String title, String message, Double workDone, double total, Boolean cancellable) { + if (progressHandle != null) { + progressHandle.finish(); + } + progressHandle = cancellable + ? ProgressHandleFactory.createHandle(title, this::requestCancel) + : ProgressHandleFactory.createHandle(title); - @NbBundle.Messages("RebuildTagsWorker.task.displayName=refreshing tags") - RebuildTagsWorker() { - super(Bundle.RebuildTagsWorker_task_displayName()); + if (workDone < 0) { + progressHandle.start(); + } else { + progressHandle.start((int) total); + } + updateTitle(title); + updateMessage(message); + updateProgress(workDone, total); } @Override - @NbBundle.Messages({"progressWindow.msg.refreshingFileTags=refreshing file tags", - "progressWindow.msg.refreshingResultTags=refreshing result tags", - "progressWindow.msg.commitingTags=committing tag changes"}) + @NbBundle.Messages({"progressWindow.msg.refreshingFileTags=Refreshing file tags", + "progressWindow.msg.refreshingResultTags=Refreshing result tags", + "progressWindow.msg.gatheringData=Gather event data", + "progressWindow.msg.commitingDb=Committing events database"}) protected Void call() throws Exception { - progressHandle.start(); - EventDB.EventTransaction trans = eventDB.beginTransaction(); - LOGGER.log(Level.INFO, "dropping old tags"); // NON-NLS - eventDB.reInitializeTags(); + EventDB.EventTransaction trans = null; + + //save paramaters for recording later + long lastObjId = skCase.getLastObjectId(); + long lastArtfID = TimeLineController.getCaseLastArtifactID(skCase); + boolean injestRunning = IngestManager.getInstance().isIngestRunning(); + + if (dbPopulationMode == DBPopulationMode.FULL) { + //drop old db, and add back MAC and artifact events + LOGGER.log(Level.INFO, "Beginning population of timeline db."); // NON-NLS + restartProgressHandle(Bundle.progressWindow_msg_gatheringData(), "", -1D, 1, true); + //reset database //TODO: can we do more incremental updates? -jm + eventDB.reInitializeDB(); + //grab ids of all files + List fileIDs = skCase.findAllFileIdsWhere("name != '.' AND name != '..'"); + final int numFiles = fileIDs.size(); + + trans = eventDB.beginTransaction(); + insertMACTimeEvents(numFiles, fileIDs, trans); + insertArtifactDerivedEvents(trans); + } + + //tags + if (dbPopulationMode == DBPopulationMode.TAGS_ONLY) { + trans = eventDB.beginTransaction(); + LOGGER.log(Level.INFO, "dropping old tags"); // NON-NLS + eventDB.reInitializeTags(); + } LOGGER.log(Level.INFO, "updating content tags"); // NON-NLS List contentTags = tagsManager.getAllContentTags(); - progressHandle.finish(); - progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_msg_refreshingFileTags(), this::cancel); - updateTitle(Bundle.progressWindow_msg_refreshingFileTags()); int currentWorkTotal = contentTags.size(); - progressHandle.start(currentWorkTotal); - - for (int i = 0; i < currentWorkTotal; i++) { - if (isCancelled()) { - break; - } - updateProgress(i, currentWorkTotal); - ContentTag contentTag = contentTags.get(i); - eventDB.addTag(contentTag.getContent().getId(), null, contentTag); - } + restartProgressHandle(Bundle.progressWindow_msg_refreshingFileTags(), "", 0D, currentWorkTotal, true); + insertContentTags(currentWorkTotal, contentTags, trans); LOGGER.log(Level.INFO, "updating artifact tags"); // NON-NLS List artifactTags = tagsManager.getAllBlackboardArtifactTags(); - progressHandle.finish(); - progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_msg_refreshingResultTags(), this::cancel); - updateTitle(Bundle.progressWindow_msg_refreshingResultTags()); currentWorkTotal = artifactTags.size(); - progressHandle.start(currentWorkTotal); - for (int i = 0; i < currentWorkTotal; i++) { - if (isCancelled()) { - break; - } - updateProgress(i, currentWorkTotal); - BlackboardArtifactTag artifactTag = artifactTags.get(i); - eventDB.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag); - } + restartProgressHandle(Bundle.progressWindow_msg_refreshingResultTags(), "", 0D, currentWorkTotal, true); + insertArtifactTags(currentWorkTotal, artifactTags, trans); - LOGGER.log(Level.INFO, "committing tags"); // NON-NLS - progressHandle.finish(); - progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_msg_commitingTags()); - progressHandle.start(); - updateTitle(Bundle.progressWindow_msg_commitingTags()); - updateProgress(-.5, 1); - - if (isCancelled()) { + LOGGER.log(Level.INFO, "committing db"); // NON-NLS + Platform.runLater(() -> cancellable.set(false)); + restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false); + if (isCancelRequested()) { eventDB.rollBackTransaction(trans); } else { eventDB.commitTransaction(trans); + recordDBPopulationState(lastObjId, lastArtfID, injestRunning); } eventDB.analyze(); populateFilterData(skCase); invalidateCaches(); progressHandle.finish(); + if (isCancelRequested()) { + cancel(); + } return null; } - @Override - @NbBundle.Messages("msgdlg.tagsproblem.text=There was a problem refreshing the tagged events." - + " Some events may have inacurate tags. See the log for details.") - protected void done() { - super.done(); - try { - get(); - } catch (CancellationException ex) { - LOGGER.log(Level.WARNING, "Timeline database population was cancelled by the user. " - + "Not all events may be present or accurate."); // NON-NLS - } catch (Exception ex) { - LOGGER.log(Level.WARNING, "Unexpected exception while populating database.", ex); // NON-NLS - JOptionPane.showMessageDialog(null, Bundle.msgdlg_tagsproblem_text()); - } - } - } - - private class DBPopulationWorker extends DBProgressWorker { - - @NbBundle.Messages("DBPopulationWorker.task.displayName=(re)initializing events database") - DBPopulationWorker() { - super(Bundle.DBPopulationWorker_task_displayName()); - } - - @Override - @NbBundle.Messages({"progressWindow.msg.populateMacEventsFiles=Populating MAC time events for files", - "progressWindow.msg.reinit_db=(Re)Initializing events database", - "progressWindow.msg.gatheringData=Gather event data", - "progressWindow.msg.commitingDb=committing events db"}) - protected Void call() throws Exception { - LOGGER.log(Level.INFO, "Beginning population of timeline db."); // NON-NLS - progressHandle.start(); - updateProgress(-.5, 1); - updateTitle(Bundle.progressWindow_msg_reinit_db()); - //reset database //TODO: can we do more incremental updates? -jm - eventDB.reInitializeDB(); - - updateTitle(Bundle.progressWindow_msg_gatheringData()); - long lastObjId = skCase.getLastObjectId(); - long lastArtfID = TimeLineController.getCaseLastArtifactID(skCase); - boolean injestRunning = IngestManager.getInstance().isIngestRunning(); - - //grab ids of all files - List fileIDs = skCase.findAllFileIdsWhere("name != '.' AND name != '..'"); - final int numFiles = fileIDs.size(); - progressHandle.switchToDeterminate(numFiles); - updateTitle(Bundle.progressWindow_msg_populateMacEventsFiles()); - - //insert file events into db - EventDB.EventTransaction trans = eventDB.beginTransaction(); - for (int i = 0; i < numFiles; i++) { - if (isCancelled()) { + private void insertArtifactTags(int currentWorkTotal, List artifactTags, EventDB.EventTransaction trans) { + for (int i = 0; i < currentWorkTotal; i++) { + if (isCancelRequested()) { break; - } else { - long fID = fileIDs.get(i); - try { - AbstractFile f = skCase.getAbstractFileById(fID); - - if (isNull(f)) { - LOGGER.log(Level.WARNING, "Failed to get data for file : {0}", fID); // NON-NLS - } else { - insertEventsForFile(f, trans); - updateProgress(i, numFiles); - updateMessage(f.getName()); - } - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.SEVERE, "Failed to insert MAC time events for file : " + fID, tskCoreException); // NON-NLS - } } + updateProgress(i, currentWorkTotal); + BlackboardArtifactTag artifactTag = artifactTags.get(i); + eventDB.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag, trans); } + } + private void insertContentTags(int currentWorkTotal, List contentTags, EventDB.EventTransaction trans) { + for (int i = 0; i < currentWorkTotal; i++) { + if (isCancelRequested()) { + break; + } + updateProgress(i, currentWorkTotal); + ContentTag contentTag = contentTags.get(i); + eventDB.addTag(contentTag.getContent().getId(), null, contentTag, trans); + } + } + + private void insertArtifactDerivedEvents(EventDB.EventTransaction trans) { //insert artifact based events //TODO: use (not-yet existing api) to grab all artifacts with timestamps, rather than the hardcoded lists in EventType -jm for (EventType type : RootEventType.allTypes) { - if (isCancelled()) { + if (isCancelRequested()) { break; } //skip file_system events, they are already handled above. @@ -611,25 +556,30 @@ public class EventsRepository { populateEventType((ArtifactEventType) type, trans); } } + } - progressHandle.finish(); - progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_msg_commitingDb()); - progressHandle.start(); - updateProgress(-0.5, 1); - updateTitle(Bundle.progressWindow_msg_commitingDb()); + @NbBundle.Messages("progressWindow.msg.populateMacEventsFiles=Populating MAC time events for files") + private void insertMACTimeEvents(final int numFiles, List fileIDs, EventDB.EventTransaction trans) { + restartProgressHandle(Bundle.progressWindow_msg_populateMacEventsFiles(), "", 0D, numFiles, true); + for (int i = 0; i < numFiles; i++) { + if (isCancelRequested()) { + break; + } + long fID = fileIDs.get(i); + try { + AbstractFile f = skCase.getAbstractFileById(fID); - if (isCancelled()) { - eventDB.rollBackTransaction(trans); - } else { - eventDB.commitTransaction(trans); + if (isNull(f)) { + LOGGER.log(Level.WARNING, "Failed to get data for file : {0}", fID); // NON-NLS + } else { + insertEventsForFile(f, trans); + updateProgress(i, numFiles); + updateMessage(f.getName()); + } + } catch (TskCoreException tskCoreException) { + LOGGER.log(Level.SEVERE, "Failed to insert MAC time events for file : " + fID, tskCoreException); // NON-NLS + } } - eventDB.analyze(); - populateFilterData(skCase); - invalidateCaches(); - - recordDBPopulationState(lastObjId, lastArtfID, injestRunning); - progressHandle.finish(); - return null; } private void insertEventsForFile(AbstractFile f, EventDB.EventTransaction trans) throws TskCoreException { @@ -662,11 +612,8 @@ public class EventsRepository { List tags = tagsManager.getContentTagsByContent(f); for (Map.Entry timeEntry : timeMap.entrySet()) { - /* - * if the time is legitimate ( greater than zero ) insert it - * into the db - */ if (timeEntry.getValue() > 0) { + // if the time is legitimate ( greater than zero ) insert it eventDB.insertEvent(timeEntry.getValue(), timeEntry.getKey(), datasourceID, f.getId(), null, uniquePath, medDesc, shortDesc, known, hashSets, tags, trans); @@ -704,10 +651,7 @@ public class EventsRepository { //get all the blackboard artifacts corresponding to the given event sub_type final ArrayList blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactType()); final int numArtifacts = blackboardArtifacts.size(); - progressHandle.finish(); - progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_populatingXevents(type.getDisplayName()), () -> cancel(true)); - progressHandle.start(numArtifacts); - updateTitle(Bundle.progressWindow_populatingXevents(type.getDisplayName())); + restartProgressHandle(Bundle.progressWindow_populatingXevents(type.getDisplayName()), "", 0D, numArtifacts, true); for (int i = 0; i < numArtifacts; i++) { try { //for each artifact, extract the relevant information for the descriptions @@ -724,10 +668,8 @@ public class EventsRepository { private void insertEventForArtifact(final ArtifactEventType type, BlackboardArtifact bbart, EventDB.EventTransaction trans) throws TskCoreException { ArtifactEventType.AttributeEventDescription eventDescription = ArtifactEventType.buildEventDescription(type, bbart); - /* - * if the time is legitimate ( greater than zero ) insert it into - * the db - */ + + // if the time is legitimate ( greater than zero ) insert it into the db if (eventDescription != null && eventDescription.getTime() > 0) { long objectID = bbart.getObjectID(); AbstractFile f = skCase.getAbstractFileById(objectID); From 7f9e8e406d68c07a15a0c857b3db0b35a72331a3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 18 Nov 2015 14:30:53 -0500 Subject: [PATCH 3/5] don't bother rolling back timeline updates if the task is cancelled, just don't recorddb population state updates. --- .../sleuthkit/autopsy/timeline/db/EventsRepository.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index cfb4a0eb42..05d3551e05 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -505,12 +505,11 @@ public class EventsRepository { LOGGER.log(Level.INFO, "committing db"); // NON-NLS Platform.runLater(() -> cancellable.set(false)); restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false); - if (isCancelRequested()) { - eventDB.rollBackTransaction(trans); - } else { - eventDB.commitTransaction(trans); + eventDB.commitTransaction(trans); + if (isCancelRequested() == false) { recordDBPopulationState(lastObjId, lastArtfID, injestRunning); } + eventDB.analyze(); populateFilterData(skCase); invalidateCaches(); From 3d5592a994fc74664ee11d445dbe0154e8f334ba Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 18 Nov 2015 14:32:37 -0500 Subject: [PATCH 4/5] consolidate timeline db update prompts to single dialog with expandable details section refactor prompting logic to gather reasons and then present single prompt rename TimeLineController properties to follow conventions --- .../autopsy/timeline/PromptDialogManager.java | 152 ++++++------------ .../autopsy/timeline/TimeLineController.java | 148 +++++++++-------- .../autopsy/timeline/WrappingListCell.java | 29 ++++ .../autopsy/timeline/ui/StatusBar.java | 6 +- 4 files changed, 165 insertions(+), 170 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/WrappingListCell.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java index a864ab2687..24b1d98bda 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -7,18 +7,21 @@ package org.sleuthkit.autopsy.timeline; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.logging.Level; +import javafx.collections.FXCollections; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Dialog; import javafx.scene.control.DialogPane; +import javafx.scene.control.ListView; import javafx.scene.image.Image; import javafx.stage.Modality; import javafx.stage.Stage; -import javafx.stage.StageStyle; import org.controlsfx.dialog.ProgressDialog; +import org.controlsfx.tools.Borders; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -29,6 +32,11 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; public class PromptDialogManager { private static final Logger LOGGER = Logger.getLogger(PromptDialogManager.class.getName()); + + private static final ButtonType SHOW_TIMELINE = new ButtonType("Show Timeline", ButtonBar.ButtonData.OK_DONE); + private static final ButtonType CONTINUE_NO_UPDATE = new ButtonType("Continue Without Updating", ButtonBar.ButtonData.CANCEL_CLOSE); + private static final ButtonType UPDATE = new ButtonType("Update", ButtonBar.ButtonData.OK_DONE); + private static final Image LOGO; static { @@ -36,7 +44,7 @@ public class PromptDialogManager { try { x = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to laod branded icon for progress dialog.", ex); + LOGGER.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); } LOGO = x; } @@ -49,11 +57,20 @@ public class PromptDialogManager { } @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"Timeline.progressWindow.title=Populating Timeline Data"}) + boolean bringCurrentDialogToFront() { + if (currentDialog != null && currentDialog.isShowing()) { + ((Stage) currentDialog.getDialogPane().getScene().getWindow()).toFront(); + return true; + } + return false; + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + @NbBundle.Messages({"PromptDialogManager.progressDialog.title=Populating Timeline Data"}) public void showProgressDialog(CancellationProgressTask task) { currentDialog = new ProgressDialog(task); - currentDialog.setTitle(Bundle.Timeline_progressWindow_title()); + currentDialog.setTitle(Bundle.PromptDialogManager_progressDialog_title()); currentDialog.headerTextProperty().bind(task.titleProperty()); DialogPane dialogPane = currentDialog.getDialogPane(); @@ -81,81 +98,6 @@ public class PromptDialogManager { currentDialog.show(); } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - boolean showConfirmationDialog(String title, String headerText, String contentText, ButtonType okButton, ButtonType cancelButton) { - currentDialog = new Alert(Alert.AlertType.CONFIRMATION, contentText, okButton, cancelButton); - currentDialog.initStyle(StageStyle.UTILITY); - currentDialog.initModality(Modality.APPLICATION_MODAL); -// alert.initOwner(mainFrame); - - currentDialog.setHeaderText(headerText); - Stage stage = (Stage) currentDialog.getDialogPane().getScene().getWindow(); - stage.setTitle(title); -// alert = alert; - return currentDialog.showAndWait().map(okButton::equals).orElse(false); - } - - /** - * prompt the user to rebuild the db because that datasource_ids are missing - * from the database and that the datasource filter will not work - * - * @return true if they agree to rebuild - */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"datasource.missing.header=The Timeline events database was previously populated without datasource information." - + "\nThe data source filter will be unavailable unless you update the events database." - }) - synchronized boolean confirmDataSourceIDsMissingRebuild() { - return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), - Bundle.datasource_missing_header(), - Bundle.TimeLinecontroller_updateNowQuestion(), - new ButtonType("Update", ButtonBar.ButtonData.OK_DONE), - new ButtonType("Continue without updating", ButtonBar.ButtonData.CANCEL_CLOSE)); - } - - /** - * prompt the user to rebuild the db because the db was last build during - * ingest and may be incomplete - * - * @return true if they agree to rebuild - */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"Timeline.do_repopulate.msg=The Timeline events database was previously populated while ingest was running." - + "\nSome events may not have been populated or may have been populated inaccurately." - }) - synchronized boolean confirmLastBuiltDuringIngestRebuild() { - return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), - Bundle.Timeline_do_repopulate_msg(), - Bundle.TimeLinecontroller_updateNowQuestion(), - new ButtonType("Update", ButtonBar.ButtonData.OK_DONE), - new ButtonType("Continue without updating", ButtonBar.ButtonData.CANCEL_CLOSE)); -// return JOptionPane.showConfirmDialog(mainFrame, -// Bundle.Timeline_do_repopulate_msg(), -// Bundle.Timeline_confirmation_dialogs_title(), -// JOptionPane.YES_NO_OPTION, -// JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION; - } - - /** - * prompt the user to rebuild the db because the db is out of date and - * doesn't include things from subsequent ingests - * - * @return true if they agree to rebuild - */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"Timeline.propChg.confDlg.timelineOOD.msg=The event data is out of date.",}) - synchronized boolean confirmOutOfDateRebuild() { - return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), - Bundle.Timeline_propChg_confDlg_timelineOOD_msg(), - Bundle.TimeLinecontroller_updateNowQuestion(), - new ButtonType("Update", ButtonBar.ButtonData.OK_DONE), - new ButtonType("Continue without updating", ButtonBar.ButtonData.CANCEL_CLOSE)); -// return JOptionPane.showConfirmDialog(mainFrame, -// Bundle.Timeline_propChg_confDlg_timelineOOD_msg(), -// Bundle.Timeline_confirmation_dialogs_title(), -// JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; - } - /** * prompt the user that ingest is running and the db may not end up * complete. @@ -163,27 +105,39 @@ public class PromptDialogManager { * @return true if they want to continue anyways */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"Timeline.initTimeline.confDlg.genBeforeIngest.msg=You are trying to generate a timeline before ingest has been completed. " - + "The timeline may be incomplete.", - "Timeline.initTimeline.confDlg.genBeforeIngest.question=Do you want to continue?"}) - synchronized boolean confirmRebuildDuringIngest() { - return showConfirmationDialog(Bundle.Timeline_confirmation_dialogs_title(), - Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(), - Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_question(), - new ButtonType("Show Timeline", ButtonBar.ButtonData.OK_DONE), - new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE)); -// return JOptionPane.showConfirmDialog(mainFrame, -// Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(), -// Bundle.Timeline_confirmation_dialogs_title(), -// JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; + @NbBundle.Messages({"PromptDialogManager.confirmDuringIngest.headerText=You are trying to generate a timeline before ingest has been completed." + + "\nThe timeline may be incomplete.", + "PromptDialogManager.confirmDuringIngest.contentText=Do you want to continue?"}) + synchronized boolean confirmDuringIngest() { + currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.PromptDialogManager_confirmDuringIngest_contentText(), SHOW_TIMELINE, ButtonType.CANCEL); + currentDialog.initModality(Modality.APPLICATION_MODAL); + currentDialog.setHeaderText(Bundle.PromptDialogManager_confirmDuringIngest_headerText()); + brandDialog(); + return currentDialog.showAndWait().map(SHOW_TIMELINE::equals).orElse(false); } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - boolean bringCurrentDialogToFront() { - if (currentDialog != null && currentDialog.isShowing()) { - ((Stage) currentDialog.getDialogPane().getScene().getWindow()).toFront(); - return true; - } - return false; + private void brandDialog() { + Stage stage = (Stage) currentDialog.getDialogPane().getScene().getWindow(); + stage.setTitle(Bundle.Timeline_confirmation_dialogs_title()); + stage.getIcons().setAll(LOGO); + } + + @NbBundle.Messages({"PromptDialogManager.rebuildPrompt.headerText=The Timeline database is incomplete and/or out of date." + + "\nSome events may be missing or inaccurate and some features may be unavailable.", + "PromptDialogManager.rebuildPrompt.details=Details:"}) + boolean confirmRebuild(ArrayList rebuildReasons) { + currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.TimeLinecontroller_updateNowQuestion(), UPDATE, CONTINUE_NO_UPDATE); + currentDialog.initModality(Modality.APPLICATION_MODAL); + currentDialog.setHeaderText(Bundle.PromptDialogManager_rebuildPrompt_headerText()); + brandDialog(); + + DialogPane dialogPane = currentDialog.getDialogPane(); + ListView listView = new ListView<>(FXCollections.observableArrayList(rebuildReasons)); + listView.setCellFactory(lstView -> new WrappingListCell()); + listView.setMaxHeight(75); + Node wrappedListView = Borders.wrap(listView).lineBorder().title(Bundle.PromptDialogManager_rebuildPrompt_details()).buildAll(); + dialogPane.setExpandableContent(wrappedListView); + + return currentDialog.showAndWait().map(UPDATE::equals).orElse(false); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 294f24c339..b2abdef96f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -25,6 +25,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.text.NumberFormat; import java.time.ZoneId; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.MissingResourceException; @@ -72,6 +73,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; @@ -132,9 +134,9 @@ public class TimeLineController { private final ReadOnlyListWrapper> tasks = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); - private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(-1); + private final ReadOnlyDoubleWrapper taskProgress = new ReadOnlyDoubleWrapper(-1); - private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper taskMessage = new ReadOnlyStringWrapper(); private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper(); @@ -176,15 +178,15 @@ public class TimeLineController { return tasks.getReadOnlyProperty(); } - synchronized public ReadOnlyDoubleProperty getProgress() { - return progress.getReadOnlyProperty(); + synchronized public ReadOnlyDoubleProperty taskProgressProperty() { + return taskProgress.getReadOnlyProperty(); } - synchronized public ReadOnlyStringProperty getMessage() { - return message.getReadOnlyProperty(); + synchronized public ReadOnlyStringProperty taskMessageProperty() { + return taskMessage.getReadOnlyProperty(); } - synchronized public ReadOnlyStringProperty getTaskTitle() { + synchronized public ReadOnlyStringProperty taskTitleProperty() { return taskTitle.getReadOnlyProperty(); } @@ -309,18 +311,12 @@ public class TimeLineController { /** * rebuld the repo. * - * @return False if the repo was not rebuilt because of an error or because - * the user aborted after prompt about ingest running. True if the - * repo was rebuilt. + * @return False if the repo was not rebuilt because because the user + * aborted after prompt about ingest running. True if the repo was + * rebuilt. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - boolean rebuildRepo() { - if (IngestManager.getInstance().isIngestRunning()) { - //confirm timeline during ingest - if (promptDialogManager.confirmRebuildDuringIngest() == false) { - return false; - } - } + void rebuildRepo() { SwingUtilities.invokeLater(this::closeTimelineWindow); final CancellationProgressTask rebuildRepository = eventsRepository.rebuildRepository(); rebuildRepository.stateProperty().addListener((stateProperty, oldState, newSate) -> { @@ -339,7 +335,7 @@ public class TimeLineController { } }); promptDialogManager.showProgressDialog(rebuildRepository); - return true; + } /** @@ -406,61 +402,78 @@ public class TimeLineController { if (promptDialogManager.bringCurrentDialogToFront()) { return; } - - boolean repoRebuilt = false; //has the repo been rebuilt - long timeLineLastObjectId = eventsRepository.getLastObjID(); - - //if the repo is empty rebuild it - if (timeLineLastObjectId == -1) { - repoRebuilt = rebuildRepo(); - } - - if (repoRebuilt == false) { - //if ingest was running uring last rebuild, prompt to rebuild - if (eventsRepository.getWasIngestRunning()) { - if (promptDialogManager.confirmLastBuiltDuringIngestRebuild()) { - repoRebuilt = rebuildRepo(); - } - } - } - - if (repoRebuilt == false) { - final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase(); - //if the last artifact and object ids don't match between skc and tldb, prompt to rebuild - if (sleuthkitCase.getLastObjectId() != timeLineLastObjectId - || getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) { - if (promptDialogManager.confirmOutOfDateRebuild()) { - repoRebuilt = rebuildRepo(); - } - } - } - - if (repoRebuilt == false) { - // if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild - if (eventsRepository.hasNewColumns() == false) { - if (promptDialogManager.confirmDataSourceIDsMissingRebuild()) { - repoRebuilt = rebuildRepo(); - } + if (IngestManager.getInstance().isIngestRunning()) { + //confirm timeline during ingest + if (promptDialogManager.confirmDuringIngest() == false) { + return; } } /* * if the repo was not rebuilt at minimum rebuild the tags which - * may have been updated without our knowing it. + * may have been updated without our knowing it, since we + * can't/aren't checking them. This should at elast be quick. + * //TODO: can we check the tags to see if we need to do this? */ - if (repoRebuilt == false) { + if (checkAndPromptForRebuild() == false) { rebuildTagsTable(); } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error when generating timeline, ", ex); // NON-NLS } catch (HeadlessException | MissingResourceException ex) { LOGGER.log(Level.SEVERE, "Unexpected error when generating timeline, ", ex); // NON-NLS } }); } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private boolean checkAndPromptForRebuild() { + //if the repo is empty just (r)ebuild it with out asking, they can always cancel part way through; + if (eventsRepository.getLastObjID() == -1) { + rebuildRepo(); + return true; + } + + ArrayList rebuildReasons = getRebuildReasons(); + if (rebuildReasons.isEmpty() == false) { + if (promptDialogManager.confirmRebuild(rebuildReasons)) { + rebuildRepo(); + return true; + } + } + return false; + } + + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) + private ArrayList getRebuildReasons() { + ArrayList rebuildReasons = new ArrayList<>(); + //if ingest was running during last rebuild, prompt to rebuild + if (eventsRepository.getWasIngestRunning()) { + rebuildReasons.add("The Timeline events database was previously populated while ingest was running:" + + " Some events may be missing, incomplete, or inaccurate"); + } + final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase(); + try { + //if the last artifact and object ids don't match between skc and tldb, prompt to rebuild + if (sleuthkitCase.getLastObjectId() != eventsRepository.getLastObjID() + || getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) { + rebuildReasons.add("The event data is out of date: Not all events will be visible."); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error determing last object id from sleutkit case. We will assume the timeline is out of date.", ex); // NON-NLS + MessageNotifyUtil.Notify.error("Timeline error.", + "Error determing if the timeline is out of date. We will assume it should be updated. See the logs for more details."); + rebuildReasons.add("Could not determine if the timeline data is out of date."); + } + // if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild + if (eventsRepository.hasNewColumns() == false) { + rebuildReasons.add("The Timeline events database was previously populated without incomplete information:" + + " Some features may be unavailable or non-functional unless you update the events database."); + } + return rebuildReasons; + } + public static long getCaseLastArtifactID(final SleuthkitCase sleuthkitCase) { + //TODO: push this into sleuthkitCase long caseLastArtfId = -1; String query = "select Max(artifact_id) as max_id from blackboard_artifacts"; // NON-NLS try (CaseDbQuery dbQuery = sleuthkitCase.executeQuery(query)) { @@ -523,6 +536,7 @@ public class TimeLineController { synchronized (TimeLineController.this) { selectedTimeRange.set(get()); selectedEventIDs.setAll(events); + } } catch (InterruptedException ex) { Logger.getLogger(FilteredEventsModel.class @@ -656,6 +670,7 @@ public class TimeLineController { synchronized (TimeLineController.this) { selectedTimeRange.set(timeRange); selectedEventIDs.setAll(get()); + } } catch (InterruptedException ex) { Logger.getLogger(FilteredEventsModel.class @@ -692,16 +707,16 @@ public class TimeLineController { case FAILED: tasks.remove(task); if (tasks.isEmpty() == false) { - progress.bind(tasks.get(0).progressProperty()); - message.bind(tasks.get(0).messageProperty()); + taskProgress.bind(tasks.get(0).progressProperty()); + taskMessage.bind(tasks.get(0).messageProperty()); taskTitle.bind(tasks.get(0).titleProperty()); } break; } }); tasks.add(task); - progress.bind(task.progressProperty()); - message.bind(task.messageProperty()); + taskProgress.bind(task.progressProperty()); + taskMessage.bind(task.messageProperty()); taskTitle.bind(task.titleProperty()); switch (task.getState()) { case READY: @@ -715,8 +730,8 @@ public class TimeLineController { case FAILED: tasks.remove(task); if (tasks.isEmpty() == false) { - progress.bind(tasks.get(0).progressProperty()); - message.bind(tasks.get(0).messageProperty()); + taskProgress.bind(tasks.get(0).progressProperty()); + taskMessage.bind(tasks.get(0).messageProperty()); taskTitle.bind(tasks.get(0).titleProperty()); } break; @@ -754,11 +769,8 @@ public class TimeLineController { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void confirmOutOfDateRebuildIfWindowOpen() throws MissingResourceException, HeadlessException { if (isWindowOpen()) { - Platform.runLater(() -> { - if (promptDialogManager.confirmOutOfDateRebuild()) { - rebuildRepo(); - } - }); + Platform.runLater(this::checkAndPromptForRebuild); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/WrappingListCell.java b/Core/src/org/sleuthkit/autopsy/timeline/WrappingListCell.java new file mode 100644 index 0000000000..06932ea2f4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/WrappingListCell.java @@ -0,0 +1,29 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.sleuthkit.autopsy.timeline; + +import javafx.scene.control.ListCell; +import javafx.scene.text.Text; + +/** + * + */ +class WrappingListCell extends ListCell { + + @Override + public void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (item == null || isEmpty()) { + setGraphic(null); + } else { + Text text = new Text(item); + text.wrappingWidthProperty().bind(getListView().widthProperty().subtract(20)); + setGraphic(text); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java index ce8b2b3e5c..3ff0222beb 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java @@ -76,9 +76,9 @@ public class StatusBar extends ToolBar { refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag()); refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag()); - taskLabel.textProperty().bind(this.controller.getTaskTitle()); - messageLabel.textProperty().bind(this.controller.getMessage()); - progressBar.progressProperty().bind(this.controller.getProgress()); + taskLabel.textProperty().bind(this.controller.taskTitleProperty()); + messageLabel.textProperty().bind(this.controller.taskMessageProperty()); + progressBar.progressProperty().bind(this.controller.taskProgressProperty()); taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not()); statusLabel.textProperty().bind(this.controller.getStatusProperty()); From 8ca30ecef586917cddd2f1ffa5a35fe49ffd6066 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 18 Nov 2015 15:29:24 -0500 Subject: [PATCH 5/5] comments, cleanup, bundlization --- .../autopsy/timeline/PromptDialogManager.java | 87 ++++++++++++------- .../autopsy/timeline/TimeLineController.java | 56 ++++++------ 2 files changed, 81 insertions(+), 62 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java index 24b1d98bda..ef249ff1e8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit 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; @@ -27,24 +40,30 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** - * + * Manager for the various prompts Timeline shows the user related to rebuilding + * the database. */ public class PromptDialogManager { private static final Logger LOGGER = Logger.getLogger(PromptDialogManager.class.getName()); - private static final ButtonType SHOW_TIMELINE = new ButtonType("Show Timeline", ButtonBar.ButtonData.OK_DONE); - private static final ButtonType CONTINUE_NO_UPDATE = new ButtonType("Continue Without Updating", ButtonBar.ButtonData.CANCEL_CLOSE); - private static final ButtonType UPDATE = new ButtonType("Update", ButtonBar.ButtonData.OK_DONE); + @NbBundle.Messages("PrompDialogManager.buttonType.showTimeline=Show Timeline") + private static final ButtonType SHOW_TIMELINE = new ButtonType(Bundle.PrompDialogManager_buttonType_showTimeline(), ButtonBar.ButtonData.OK_DONE); + + @NbBundle.Messages("PrompDialogManager.buttonType.continueNoUpdate=Continue Without Updating") + private static final ButtonType CONTINUE_NO_UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_continueNoUpdate(), ButtonBar.ButtonData.CANCEL_CLOSE); + + @NbBundle.Messages("PrompDialogManager.buttonType.update=Update") + private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE); private static final Image LOGO; static { Image x = null; try { - x = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); + x = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NOI18N } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); + LOGGER.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NOI18N } LOGO = x; } @@ -65,26 +84,24 @@ public class PromptDialogManager { return false; } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @NbBundle.Messages({"PromptDialogManager.progressDialog.title=Populating Timeline Data"}) + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public void showProgressDialog(CancellationProgressTask task) { - currentDialog = new ProgressDialog(task); - currentDialog.setTitle(Bundle.PromptDialogManager_progressDialog_title()); currentDialog.headerTextProperty().bind(task.titleProperty()); + setDialogIcons(currentDialog); + currentDialog.setTitle(Bundle.PromptDialogManager_progressDialog_title()); DialogPane dialogPane = currentDialog.getDialogPane(); - dialogPane.setPrefWidth(400); + dialogPane.setPrefSize(400, 200); //override autosizing which fails for some reason - dialogPane.setPrefHeight(200); + //co-ordinate task cancelation and dialog hiding. task.setOnCancelled(cancelled -> currentDialog.close()); task.setOnSucceeded(succeeded -> currentDialog.close()); - dialogPane.getButtonTypes().setAll(ButtonType.CANCEL); final Node cancelButton = dialogPane.lookupButton(ButtonType.CANCEL); cancelButton.disableProperty().bind(task.cancellableProperty().not()); currentDialog.setOnCloseRequest(closeRequest -> { - if (task.isRunning()) { closeRequest.consume(); } @@ -93,43 +110,49 @@ public class PromptDialogManager { } }); - Stage stage = (Stage) dialogPane.getScene().getWindow(); - stage.getIcons().setAll(LOGO); currentDialog.show(); } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + static private void setDialogIcons(Dialog dialog) { + Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow(); + stage.getIcons().setAll(LOGO); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + static private void setDialogTitle(Dialog dialog) { + Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow(); + stage.setTitle(Bundle.Timeline_confirmation_dialogs_title()); + } + /** * prompt the user that ingest is running and the db may not end up * complete. * * @return true if they want to continue anyways */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - @NbBundle.Messages({"PromptDialogManager.confirmDuringIngest.headerText=You are trying to generate a timeline before ingest has been completed." - + "\nThe timeline may be incomplete.", + @NbBundle.Messages({"PromptDialogManager.confirmDuringIngest.headerText=You are trying to show a timeline before ingest has been completed.\nThe timeline may be incomplete.", "PromptDialogManager.confirmDuringIngest.contentText=Do you want to continue?"}) - synchronized boolean confirmDuringIngest() { + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + boolean confirmDuringIngest() { currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.PromptDialogManager_confirmDuringIngest_contentText(), SHOW_TIMELINE, ButtonType.CANCEL); currentDialog.initModality(Modality.APPLICATION_MODAL); currentDialog.setHeaderText(Bundle.PromptDialogManager_confirmDuringIngest_headerText()); - brandDialog(); + setDialogIcons(currentDialog); + setDialogTitle(currentDialog); + return currentDialog.showAndWait().map(SHOW_TIMELINE::equals).orElse(false); } - private void brandDialog() { - Stage stage = (Stage) currentDialog.getDialogPane().getScene().getWindow(); - stage.setTitle(Bundle.Timeline_confirmation_dialogs_title()); - stage.getIcons().setAll(LOGO); - } - - @NbBundle.Messages({"PromptDialogManager.rebuildPrompt.headerText=The Timeline database is incomplete and/or out of date." - + "\nSome events may be missing or inaccurate and some features may be unavailable.", + @NbBundle.Messages({"PromptDialogManager.rebuildPrompt.headerText=The Timeline database is incomplete and/or out of date.\nSome events may be missing or inaccurate and some features may be unavailable.", "PromptDialogManager.rebuildPrompt.details=Details:"}) + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean confirmRebuild(ArrayList rebuildReasons) { currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.TimeLinecontroller_updateNowQuestion(), UPDATE, CONTINUE_NO_UPDATE); currentDialog.initModality(Modality.APPLICATION_MODAL); currentDialog.setHeaderText(Bundle.PromptDialogManager_rebuildPrompt_headerText()); - brandDialog(); + setDialogIcons(currentDialog); + setDialogTitle(currentDialog); DialogPane dialogPane = currentDialog.getDialogPane(); ListView listView = new ListView<>(FXCollections.observableArrayList(rebuildReasons)); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index b2abdef96f..f8f4f029e2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -119,7 +119,7 @@ public class TimeLineController { } public static DateTimeFormatter getZonedFormatter() { - return DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone()); // NON-NLS + return DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss").withZone(getJodaTimeZone()); // NON-NLS //NOI18N } public static DateTimeZone getJodaTimeZone() { @@ -420,7 +420,7 @@ public class TimeLineController { } } catch (HeadlessException | MissingResourceException ex) { - LOGGER.log(Level.SEVERE, "Unexpected error when generating timeline, ", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Unexpected error when generating timeline, ", ex); // NON-NLS //NOI18N } }); } @@ -444,30 +444,34 @@ public class TimeLineController { } @ThreadConfined(type = ThreadConfined.ThreadType.ANY) + @NbBundle.Messages({"TimeLineController.errorTitle=Timeline error.", + "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.", + "TimeLineController.rebuildReasons.outOfDateError=Could not determine if the timeline data is out of date.", + "TimeLineController.rebuildReasons.outOfDate=The event data is out of date: Not all events will be visible.", + "TimeLineController.rebuildReasons.ingestWasRunning=The Timeline events database was previously populated while ingest was running: Some events may be missing, incomplete, or inaccurate.", + "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."}) private ArrayList getRebuildReasons() { ArrayList rebuildReasons = new ArrayList<>(); //if ingest was running during last rebuild, prompt to rebuild if (eventsRepository.getWasIngestRunning()) { - rebuildReasons.add("The Timeline events database was previously populated while ingest was running:" - + " Some events may be missing, incomplete, or inaccurate"); + rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning()); } final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase(); try { //if the last artifact and object ids don't match between skc and tldb, prompt to rebuild if (sleuthkitCase.getLastObjectId() != eventsRepository.getLastObjID() || getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) { - rebuildReasons.add("The event data is out of date: Not all events will be visible."); + rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDate()); } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error determing last object id from sleutkit case. We will assume the timeline is out of date.", ex); // NON-NLS - MessageNotifyUtil.Notify.error("Timeline error.", - "Error determing if the timeline is out of date. We will assume it should be updated. See the logs for more details."); - rebuildReasons.add("Could not determine if the timeline data is out of date."); + LOGGER.log(Level.SEVERE, "Error determing last object id from sleutkit case. We will assume the timeline is out of date.", ex); // NON-NLS + MessageNotifyUtil.Notify.error(Bundle.TimeLineController_errorTitle(), + Bundle.TimeLineController_outOfDate_errorMessage()); + rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError()); } // if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild if (eventsRepository.hasNewColumns() == false) { - rebuildReasons.add("The Timeline events database was previously populated without incomplete information:" - + " Some features may be unavailable or non-functional unless you update the events database."); + rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema()); } return rebuildReasons; } @@ -475,14 +479,14 @@ public class TimeLineController { public static long getCaseLastArtifactID(final SleuthkitCase sleuthkitCase) { //TODO: push this into sleuthkitCase long caseLastArtfId = -1; - String query = "select Max(artifact_id) as max_id from blackboard_artifacts"; // NON-NLS + String query = "select Max(artifact_id) as max_id from blackboard_artifacts"; // NON-NLS //NOI18N try (CaseDbQuery dbQuery = sleuthkitCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { - caseLastArtfId = resultSet.getLong("max_id"); // NON-NLS + caseLastArtfId = resultSet.getLong("max_id"); // NON-NLS //NOI18N } } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error getting last artifact id: ", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Error getting last artifact id: ", ex); // NON-NLS //NOI18N } return caseLastArtfId; } @@ -523,7 +527,7 @@ public class TimeLineController { } public void selectEventIDs(Collection events) { - final LoggedTask selectEventIDsTask = new LoggedTask("Select Event IDs", true) { // NON-NLS + final LoggedTask selectEventIDsTask = new LoggedTask("Select Event IDs", true) { // NON-NLS //NOI18N @Override protected Interval call() throws Exception { return filteredEvents.getSpanningInterval(events); @@ -538,12 +542,8 @@ public class TimeLineController { selectedEventIDs.setAll(events); } - } catch (InterruptedException ex) { - Logger.getLogger(FilteredEventsModel.class - .getName()).log(Level.SEVERE, getTitle() + " interrupted unexpectedly", ex); // NON-NLS - } catch (ExecutionException ex) { - Logger.getLogger(FilteredEventsModel.class - .getName()).log(Level.SEVERE, getTitle() + " unexpectedly threw " + ex.getCause(), ex); // NON-NLS + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); // NON-NLS //NOI18N } } }; @@ -587,8 +587,7 @@ public class TimeLineController { } @NbBundle.Messages({"# {0} - the number of events", - "Timeline.pushDescrLOD.confdlg.msg=You are about to show details for {0} events." - + " This might be very slow or even crash Autopsy.\n\nDo you want to continue?", + "Timeline.pushDescrLOD.confdlg.msg=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?", "Timeline.pushDescrLOD.confdlg.title=Change description level of detail?"}) synchronized public boolean pushDescrLOD(DescriptionLoD newLOD) { Map eventCounts = filteredEvents.getEventCounts(filteredEvents.zoomParametersProperty().get().getTimeRange()); @@ -655,7 +654,7 @@ public class TimeLineController { public void selectTimeAndType(Interval interval, EventType type) { final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval); - final LoggedTask> selectTimeAndTypeTask = new LoggedTask>("Select Time and Type", true) { // NON-NLS + final LoggedTask> selectTimeAndTypeTask = new LoggedTask>("Select Time and Type", true) { // NON-NLS //NOI18N @Override protected Collection< Long> call() throws Exception { synchronized (TimeLineController.this) { @@ -672,12 +671,8 @@ public class TimeLineController { selectedEventIDs.setAll(get()); } - } catch (InterruptedException ex) { - Logger.getLogger(FilteredEventsModel.class - .getName()).log(Level.SEVERE, getTitle() + " interrupted unexpectedly", ex);// NON-NLS - } catch (ExecutionException ex) { - Logger.getLogger(FilteredEventsModel.class - .getName()).log(Level.SEVERE, getTitle() + " unexpectedly threw " + ex.getCause(), ex);// NON-NLS + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); // NON-NLS //NOI18N } } }; @@ -692,6 +687,7 @@ public class TimeLineController { * @param task */ synchronized public void monitorTask(final Task task) { + //TODO: refactor this to use JavaFX Service? -jm if (task != null) { Platform.runLater(() -> {