From 1b48afa81125f50d944beeff27f21c7838c55e3c Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 13 Nov 2015 17:19:49 -0500 Subject: [PATCH] don't block while rebuilding repo, use JFX dialogs WIP --- .../autopsy/timeline/OpenTimelineAction.java | 5 +- .../autopsy/timeline/ProgressUpdate.java | 48 ++++ .../autopsy/timeline/ProgressWindow.form | 65 ------ .../autopsy/timeline/ProgressWindow.java | 217 ------------------ .../autopsy/timeline/TimeLineController.java | 158 ++++++++----- .../autopsy/timeline/db/EventsRepository.java | 143 +++++++----- 6 files changed, 239 insertions(+), 397 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ProgressUpdate.java delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.form delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 3c5c8a6621..ad5ffe8323 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -31,6 +31,7 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false) @@ -58,8 +59,8 @@ public class OpenTimelineAction extends CallableSystemAction { } @Override + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void performAction() { - //check case if (!Case.isCaseOpen()) { return; @@ -72,14 +73,12 @@ public class OpenTimelineAction extends CallableSystemAction { LOGGER.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS return; } - synchronized (OpenTimelineAction.class) { 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/ProgressUpdate.java b/Core/src/org/sleuthkit/autopsy/timeline/ProgressUpdate.java new file mode 100644 index 0000000000..31783e2e3f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ProgressUpdate.java @@ -0,0 +1,48 @@ +/* + * 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 javax.annotation.concurrent.Immutable; + +/** + * bundles up progress information to be shown in the progress dialog + */ +@Immutable +public class ProgressUpdate { + private final int progress; + private final int total; + private final String headerMessage; + private final String detailMessage; + + public int getProgress() { + return progress; + } + + public int getTotal() { + return total; + } + + public String getHeaderMessage() { + return headerMessage; + } + + public String getDetailMessage() { + return detailMessage; + } + + public ProgressUpdate(int progress, int total, String headerMessage, String detailMessage) { + this.progress = progress; + this.total = total; + this.headerMessage = headerMessage; + this.detailMessage = detailMessage; + } + + public ProgressUpdate(int progress, int total, String headerMessage) { + this(progress, total, headerMessage, ""); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.form b/Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.form deleted file mode 100644 index 397abea1b4..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.form +++ /dev/null @@ -1,65 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.java b/Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.java deleted file mode 100644 index 9afa6f7c89..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ProgressWindow.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 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; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.KeyEvent; -import javax.annotation.concurrent.Immutable; -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JOptionPane; -import javax.swing.KeyStroke; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; - -/** - * Dialog with progress bar that pops up when timeline is being generated - */ -public class ProgressWindow extends JFrame { - - private final SwingWorker worker; - - /** - * Creates new form TimelineProgressDialog - */ - @NbBundle.Messages({"Timeline.progressWindow.name=Timeline", - "Timeline.progressWindow.title=Generating Timeline data"}) - public ProgressWindow(Component parent, boolean modal, SwingWorker worker) { - super(); - initComponents(); - - setLocationRelativeTo(parent); - - setAlwaysOnTop(modal); - - //set icon the same as main app - SwingUtilities.invokeLater(() -> { - setIconImage(WindowManager.getDefault().getMainWindow().getIconImage()); - }); - - setName(Bundle.Timeline_progressWindow_name()); - setTitle(Bundle.Timeline_progressWindow_title()); - // Close the dialog when Esc is pressed - String cancelName = "cancel"; // NON-NLS - InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); - ActionMap actionMap = getRootPane().getActionMap(); - - actionMap.put(cancelName, new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - cancel(); - } - }); - this.worker = worker; - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - progressBar = new javax.swing.JProgressBar(); - progressHeader = new javax.swing.JLabel(); - - addWindowListener(new java.awt.event.WindowAdapter() { - public void windowClosing(java.awt.event.WindowEvent evt) { - closeDialog(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(progressHeader, NbBundle.getMessage(ProgressWindow.class, "ProgressWindow.progressHeader.text")); // NOI18N - progressHeader.setMinimumSize(new java.awt.Dimension(10, 14)); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addComponent(progressHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(progressHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - - pack(); - }// //GEN-END:initComponents - - /** - * Closes the dialog - */ - private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog - cancel(); - }//GEN-LAST:event_closeDialog - - @NbBundle.Messages({"Timeline.ProgressWindow.cancel.confdlg.msg=Do you want to cancel timeline creation?", - "Timeline.ProgressWindow.cancel.confdlg.detail=Cancel timeline creation?"}) - public void cancel() { - SwingUtilities.invokeLater(() -> { - if (isVisible()) { - int showConfirmDialog = JOptionPane.showConfirmDialog(ProgressWindow.this, - Bundle.Timeline_ProgressWindow_cancel_confdlg_msg(), - Bundle.Timeline_ProgressWindow_cancel_confdlg_detail(), - JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); - if (showConfirmDialog == JOptionPane.YES_OPTION) { - close(); - } - } else { - close(); - } - }); - } - - public void close() { - worker.cancel(false); - setVisible(false); - dispose(); - } - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JProgressBar progressBar; - private javax.swing.JLabel progressHeader; - // End of variables declaration//GEN-END:variables - - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void update(ProgressUpdate chunk) { - progressHeader.setText(chunk.getHeaderMessage()); - if (chunk.getTotal() >= 0) { - progressBar.setIndeterminate(false); - progressBar.setMaximum(chunk.getTotal()); - progressBar.setStringPainted(true); - progressBar.setValue(chunk.getProgress()); - progressBar.setString(chunk.getDetailMessage()); - } else { - progressBar.setIndeterminate(true); - progressBar.setStringPainted(true); - progressBar.setString(chunk.getDetailMessage()); - } - } - - /** - * bundles up progress information to be shown in the progress dialog - */ - @Immutable - public static class ProgressUpdate { - - private final int progress; - private final int total; - private final String headerMessage; - private final String detailMessage; - - public int getProgress() { - return progress; - } - - public int getTotal() { - return total; - } - - public String getHeaderMessage() { - return headerMessage; - } - - public String getDetailMessage() { - return detailMessage; - } - - public ProgressUpdate(int progress, int total, String headerMessage, String detailMessage) { - this.progress = progress; - this.total = total; - this.headerMessage = headerMessage; - this.detailMessage = detailMessage; - } - - public ProgressUpdate(int progress, int total, String headerMessage) { - this(progress, total, headerMessage, ""); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 9f904eca79..07e8af5824 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -21,6 +21,8 @@ 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; @@ -28,6 +30,7 @@ import java.time.ZoneId; import java.util.Collection; import java.util.Map; import java.util.MissingResourceException; +import java.util.Optional; import java.util.TimeZone; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -49,10 +52,18 @@ import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; +import javafx.concurrent.WorkerStateEvent; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.DialogEvent; +import javafx.scene.image.Image; +import javafx.stage.Modality; +import javafx.stage.Stage; 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; @@ -136,6 +147,9 @@ public class TimeLineController { private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper(); + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private ProgressDialog taskProgressDialog; + /** * status is a string that will be displayed in the status bar as a kind of * user hint/information when it is not empty @@ -202,7 +216,6 @@ public class TimeLineController { @GuardedBy("filteredEvents") private final FilteredEventsModel filteredEvents; - @GuardedBy("eventsRepository") private final EventsRepository eventsRepository; @GuardedBy("this") @@ -305,6 +318,7 @@ public class TimeLineController { * the user aborted after prompt about ingest running. True if the * repo was rebuilt. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) boolean rebuildRepo() { if (IngestManager.getInstance().isIngestRunning()) { //confirm timeline during ingest @@ -314,40 +328,29 @@ public class TimeLineController { } LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS try { - SwingUtilities.invokeLater(() -> { - synchronized (TimeLineController.this) { - if (isWindowOpen()) { - mainFrame.close(); - } - } - }); + closeTimelineWindow(); final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); final long lastObjId = sleuthkitCase.getLastObjectId(); final long lastArtfID = getCaseLastArtifactID(sleuthkitCase); final Boolean injestRunning = IngestManager.getInstance().isIngestRunning(); - //TODO: verify this locking is correct? -jm - synchronized (eventsRepository) { - eventsRepository.rebuildRepository(() -> { - synchronized (eventsRepository) { - eventsRepository.recordLastObjID(lastObjId); - eventsRepository.recordLastArtifactID(lastArtfID); - eventsRepository.recordWasIngestRunning(injestRunning); - } - synchronized (TimeLineController.this) { - //TODO: this looks hacky. what is going on? should this be an event? - needsHistogramRebuild.set(true); - needsHistogramRebuild.set(false); - showWindow(); - } + final Task rebuildRepository = eventsRepository.rebuildRepository(lastObjId, lastArtfID, injestRunning); + Platform.runLater(() -> { + rebuildRepository.setOnSucceeded((WorkerStateEvent event) -> { + //this is run on jfx thread - Platform.runLater(() -> { - //TODO: should this be an event? - newEventsFlag.set(false); - historyManager.reset(filteredEvents.zoomParametersProperty().get()); - TimeLineController.this.showFullRange(); - }); + //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(); }); - } + taskProgressDialog = showProgressDialogForTask(rebuildRepository); + }); + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error when generating timeline, ", ex); // NON-NLS return false; @@ -360,25 +363,62 @@ public class TimeLineController { * tags table and rebuild it by querying for all the tags and inserting them * in to the TimeLine DB. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void rebuildTagsTable() { LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS - SwingUtilities.invokeLater(() -> { - synchronized (TimeLineController.this) { - if (isWindowOpen()) { - mainFrame.close(); - } - } - }); - synchronized (eventsRepository) { - eventsRepository.rebuildTags(() -> { - showWindow(); - Platform.runLater(() -> { - showFullRange(); - }); + closeTimelineWindow(); + Task rebuildTags = eventsRepository.rebuildTags(); + + Platform.runLater(() -> { + rebuildTags.setOnSucceeded((WorkerStateEvent event) -> { + SwingUtilities.invokeLater(TimeLineController.this::showWindow); + showFullRange();; }); + taskProgressDialog = showProgressDialogForTask(rebuildTags); + }); + + } + + private void closeTimelineWindow() { + if (isWindowOpen()) { + mainFrame.close(); } } + @NbBundle.Messages({"Timeline.progressWindow.title=Populating Timeline data", + "Timeline.ProgressWindow.cancel.confdlg.msg=Not all events will be present of accurate if you cancel the timeline population. Do you want to cancel?", + "Timeline.ProgressWindow.cancel.confdlg.detail=Cancel timeline population?"}) + private ProgressDialog showProgressDialogForTask(Task task) { + ProgressDialog progressDialog = new ProgressDialog(task); + progressDialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL); + + progressDialog.setTitle(Bundle.Timeline_progressWindow_title()); + progressDialog.getDialogPane().headerTextProperty().bind(task.titleProperty()); + try { + Image image = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); + ((Stage) progressDialog.getDialogPane().getScene().getWindow()).getIcons().setAll(image); + } catch (IOException iOException) { + LOGGER.log(Level.WARNING, "Failed to laod branded icon for progress dialog.", iOException); + } + progressDialog.setOnCloseRequest((DialogEvent event) -> { + Alert confirmation = new Alert(Alert.AlertType.CONFIRMATION, Bundle.Timeline_ProgressWindow_cancel_confdlg_msg(), ButtonType.YES, ButtonType.NO); + confirmation.setHeaderText(null); + confirmation.setTitle(Bundle.Timeline_ProgressWindow_cancel_confdlg_detail()); + confirmation.initOwner(progressDialog.getDialogPane().getScene().getWindow()); + confirmation.initModality(Modality.WINDOW_MODAL); + Optional showAndWait = confirmation.showAndWait(); + + showAndWait.ifPresent(buttonType -> { + if (buttonType == ButtonType.YES) { + task.cancel(true); + } else { + event.consume(); + } + }); + }); + return progressDialog; + } + public void showFullRange() { synchronized (filteredEvents) { pushTimeRange(filteredEvents.getSpanningInterval()); @@ -403,6 +443,7 @@ public class TimeLineController { /** * show the timeline window and prompt for rebuilding database if necessary. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void openTimeLine() { // listen for case changes (specifically images being added, and case changes). if (Case.isCaseOpen() && !listeningToAutopsy) { @@ -413,6 +454,16 @@ public class TimeLineController { } try { + if (eventsRepository.isRebuilding()) { + Platform.runLater(() -> { + if (taskProgressDialog != null) { + ((Stage) taskProgressDialog.getDialogPane().getScene().getWindow()).toFront(); + } + }); + + return; + } + boolean repoRebuilt = false; //has the repo been rebuilt long timeLineLastObjectId = eventsRepository.getLastObjID(); @@ -545,16 +596,13 @@ public class TimeLineController { /** * private method to build gui if necessary and make it visible. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) synchronized private void showWindow() { - SwingUtilities.invokeLater(() -> { - synchronized (TimeLineController.this) { - if (mainFrame == null) { - mainFrame = new TimeLineTopComponent(this); - } - mainFrame.open(); - mainFrame.toFront(); - } - }); + if (mainFrame == null) { + mainFrame = new TimeLineTopComponent(this); + } + mainFrame.open(); + mainFrame.toFront(); } synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) { @@ -575,8 +623,7 @@ public class TimeLineController { } else if (currentZoom.hasTimeRange(timeRange) == false) { advance(currentZoom.withTimeRange(timeRange)); return true; - } - else{ + } else { return false; } } @@ -590,7 +637,7 @@ public class TimeLineController { final Long count = eventCounts.values().stream().reduce(0l, Long::sum); boolean shouldContinue = true; - if ((newLOD == DescriptionLoD.FULL|| newLOD ==DescriptionLoD.MEDIUM )&& count > 10_000) { + if ((newLOD == DescriptionLoD.FULL || newLOD == DescriptionLoD.MEDIUM) && count > 10_000) { String format = NumberFormat.getInstance().format(count); int showConfirmDialog = JOptionPane.showConfirmDialog(mainFrame, @@ -748,7 +795,8 @@ public class TimeLineController { * * @return true if the timeline window is open */ - synchronized private boolean isWindowOpen() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private boolean isWindowOpen() { return mainFrame != null && mainFrame.isOpened() && mainFrame.isVisible(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index b36c4a56c6..ea083f67d9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.timeline.db; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -30,6 +31,8 @@ import java.util.Map; import static java.util.Objects.isNull; import java.util.Set; import java.util.concurrent.CancellationException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; @@ -37,10 +40,8 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; -import javax.annotation.concurrent.GuardedBy; +import javafx.concurrent.Task; import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import org.apache.commons.lang3.StringUtils; import org.controlsfx.dialog.ProgressDialog; import org.joda.time.Interval; @@ -50,7 +51,8 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.timeline.ProgressWindow; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.timeline.ProgressUpdate; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; @@ -93,8 +95,7 @@ public class EventsRepository { private final EventDB eventDB; - @GuardedBy("this") - private SwingWorker dbPopulationWorker; + private Task dbPopulationWorker; private final LoadingCache maxCache; @@ -177,15 +178,15 @@ public class EventsRepository { // return eventDB.getMinTime(); } - public void recordLastArtifactID(long lastArtfID) { + private void recordLastArtifactID(long lastArtfID) { eventDB.recordLastArtifactID(lastArtfID); } - public void recordWasIngestRunning(Boolean wasIngestRunning) { + private void recordWasIngestRunning(Boolean wasIngestRunning) { eventDB.recordWasIngestRunning(wasIngestRunning); } - public void recordLastObjID(Long lastObjID) { + private void recordLastObjID(Long lastObjID) { eventDB.recordLastObjID(lastObjID); } @@ -324,22 +325,48 @@ public class EventsRepository { } } - synchronized public void rebuildRepository(Runnable r) { + Executor workerExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("eventrepository-worker-%d").build()); + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public Task rebuildRepository(long lastObjId, long lastArtfID, Boolean injestRunning) { if (dbPopulationWorker != null) { dbPopulationWorker.cancel(true); - + LOGGER.log(Level.INFO, "cancelling previous db worker"); } - dbPopulationWorker = new DBPopulationWorker(r); - dbPopulationWorker.execute(); + LOGGER.log(Level.INFO, "rebuilding repo"); + dbPopulationWorker = new DBPopulationWorker(lastObjId, lastArtfID, injestRunning); + workerExecutor.execute(dbPopulationWorker); + return dbPopulationWorker; } - synchronized public void rebuildTags(Runnable r) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public Task rebuildTags() { if (dbPopulationWorker != null) { dbPopulationWorker.cancel(true); - + LOGGER.log(Level.INFO, "cancelling previous db worker"); } - dbPopulationWorker = new RebuildTagsWorker(r); - dbPopulationWorker.execute(); + LOGGER.log(Level.INFO, "rebuilding tags"); + dbPopulationWorker = new RebuildTagsWorker(); + workerExecutor.execute(dbPopulationWorker); + return dbPopulationWorker; + } + + /** + * + * @param lastObjId the value of lastObjId + * @param lastArtfID the value of lastArtfID + * @param injestRunning the value of injestRunning + * @param timeLineController the value of timeLineController + */ + public void recordDBPopulationState(final long lastObjId, final long lastArtfID, final Boolean injestRunning) { + recordLastObjID(lastObjId); + recordLastArtifactID(lastArtfID); + recordWasIngestRunning(injestRunning); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public boolean isRebuilding() { + return dbPopulationWorker != null && dbPopulationWorker.isDone() == false; } /** @@ -352,26 +379,19 @@ public class EventsRepository { * these to a JavaFX implementation,and replace {@link ProgressWindow} with * {@link ProgressDialog} */ - private abstract class DBProgressWorker extends SwingWorker { - - //TODO: can we avoid this with a state listener? does it amount to the same thing? - //post population operation to execute - final Runnable postPopulationOperation; + private abstract class DBProgressWorker extends Task { final SleuthkitCase skCase; final TagsManager tagsManager; - final ProgressWindow progressDialog; volatile ProgressHandle progressHandle; - DBProgressWorker(Runnable postPopulationOperation, String initialProgressDisplayName) { - progressDialog = new ProgressWindow(null, false, this); - progressDialog.setVisible(true); + DBProgressWorker(String initialProgressDisplayName) { progressHandle = ProgressHandleFactory.createHandle(initialProgressDisplayName, () -> cancel(true)); skCase = autoCase.getSleuthkitCase(); tagsManager = autoCase.getServices().getTagsManager(); - this.postPopulationOperation = postPopulationOperation; + } /** @@ -379,10 +399,11 @@ public class EventsRepository { * * @param chunk */ - final protected void update(ProgressWindow.ProgressUpdate chunk) { - SwingUtilities.invokeLater(() -> { - progressDialog.update(chunk); - }); + final protected void update(ProgressUpdate chunk) { + updateProgress(chunk.getProgress(), chunk.getTotal()); + updateMessage(chunk.getDetailMessage()); + updateTitle(chunk.getHeaderMessage()); + if (chunk.getTotal() >= 0) { progressHandle.progress(chunk.getProgress()); } @@ -390,12 +411,6 @@ public class EventsRepository { progressHandle.progress(chunk.getDetailMessage()); } - @Override - protected void done() { - super.done(); - progressDialog.close(); - postPopulationOperation.run(); //execute post db population operation - } } public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) { @@ -405,15 +420,15 @@ public class EventsRepository { private class RebuildTagsWorker extends DBProgressWorker { @NbBundle.Messages("RebuildTagsWorker.task.displayName=refreshing tags") - RebuildTagsWorker(Runnable postPopulationOperation) { - super(postPopulationOperation, Bundle.RebuildTagsWorker_task_displayName()); + RebuildTagsWorker() { + super(Bundle.RebuildTagsWorker_task_displayName()); } @Override @NbBundle.Messages({"progressWindow.msg.refreshingFileTags=refreshing file tags", "progressWindow.msg.refreshingResultTags=refreshing result tags", "progressWindow.msg.commitingTags=committing tag changes"}) - protected Void doInBackground() throws Exception { + protected Void call() throws Exception { int currentWorkTotal; progressHandle.start(); @@ -432,7 +447,7 @@ public class EventsRepository { if (isCancelled()) { break; } - update(new ProgressWindow.ProgressUpdate(i, currentWorkTotal, Bundle.progressWindow_msg_refreshingFileTags())); + update(new ProgressUpdate(i, currentWorkTotal, Bundle.progressWindow_msg_refreshingFileTags())); ContentTag contentTag = contentTags.get(i); eventDB.addTag(contentTag.getContent().getId(), null, contentTag); } @@ -448,7 +463,7 @@ public class EventsRepository { if (isCancelled()) { break; } - update(new ProgressWindow.ProgressUpdate(i, currentWorkTotal, Bundle.progressWindow_msg_refreshingResultTags())); + update(new ProgressUpdate(i, currentWorkTotal, Bundle.progressWindow_msg_refreshingResultTags())); BlackboardArtifactTag artifactTag = artifactTags.get(i); eventDB.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag); } @@ -457,7 +472,7 @@ public class EventsRepository { progressHandle.finish(); progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_msg_commitingTags()); progressHandle.start(); - update(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_commitingTags())); + update(new ProgressUpdate(0, -1, Bundle.progressWindow_msg_commitingTags())); if (isCancelled()) { eventDB.rollBackTransaction(trans); @@ -491,18 +506,25 @@ public class EventsRepository { private class DBPopulationWorker extends DBProgressWorker { + private final long lastObjId; + private final long lastArtfID; + private final boolean injestRunning; + @NbBundle.Messages("DBPopulationWorker.task.displayName=(re)initializing events database") - public DBPopulationWorker(Runnable postPopulationOperation) { - super(postPopulationOperation, Bundle.DBPopulationWorker_task_displayName()); + DBPopulationWorker(long lastObjId, long lastArtfID, Boolean injestRunning) { + super(Bundle.DBPopulationWorker_task_displayName()); + this.lastObjId = lastObjId; + this.lastArtfID = lastArtfID; + this.injestRunning = injestRunning; } @Override @NbBundle.Messages({"progressWindow.msg.populateMacEventsFiles=Populating MAC time events for files:", "progressWindow.msg.reinit_db=(re)initializing events database", "progressWindow.msg.commitingDb=committing events db"}) - protected Void doInBackground() throws Exception { + protected Void call() throws Exception { progressHandle.start(); - update(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_reinit_db())); + update(new ProgressUpdate(0, -1, Bundle.progressWindow_msg_reinit_db())); //reset database //TODO: can we do more incremental updates? -jm eventDB.reInitializeDB(); @@ -510,7 +532,7 @@ public class EventsRepository { List fileIDs = skCase.findAllFileIdsWhere("name != '.' AND name != '..'"); final int numFiles = fileIDs.size(); progressHandle.switchToDeterminate(numFiles); - update(new ProgressWindow.ProgressUpdate(0, numFiles, Bundle.progressWindow_msg_populateMacEventsFiles())); + update(new ProgressUpdate(0, numFiles, Bundle.progressWindow_msg_populateMacEventsFiles())); //insert file events into db EventDB.EventTransaction trans = eventDB.beginTransaction(); @@ -526,7 +548,7 @@ public class EventsRepository { LOGGER.log(Level.WARNING, "Failed to get data for file : {0}", fID); // NON-NLS } else { insertEventsForFile(f, trans); - update(new ProgressWindow.ProgressUpdate(i, numFiles, + update(new ProgressUpdate(i, numFiles, Bundle.progressWindow_msg_populateMacEventsFiles(), f.getName())); } } catch (TskCoreException tskCoreException) { @@ -550,7 +572,7 @@ public class EventsRepository { progressHandle.finish(); progressHandle = ProgressHandleFactory.createHandle(Bundle.progressWindow_msg_commitingDb()); progressHandle.start(); - update(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_commitingDb())); + update(new ProgressUpdate(0, -1, Bundle.progressWindow_msg_commitingDb())); if (isCancelled()) { eventDB.rollBackTransaction(trans); @@ -561,6 +583,7 @@ public class EventsRepository { populateFilterData(skCase); invalidateCaches(); + recordDBPopulationState(lastObjId, lastArtfID, injestRunning); progressHandle.finish(); return null; } @@ -573,10 +596,12 @@ public class EventsRepository { timeMap.put(FileSystemTypes.FILE_CHANGED, f.getCtime()); timeMap.put(FileSystemTypes.FILE_MODIFIED, f.getMtime()); - /* if there are no legitimate ( greater tan zero ) time stamps ( eg, + /* + * if there are no legitimate ( greater tan zero ) time stamps ( eg, * logical/local files) skip the rest of the event generation: this * should result in droping logical files, since they do not have - * legitimate time stamps. */ + * legitimate time stamps. + */ if (Collections.max(timeMap.values()) > 0) { final String uniquePath = f.getUniquePath(); final String parentPath = f.getParentPath(); @@ -593,8 +618,10 @@ 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 the time is legitimate ( greater than zero ) insert it + * into the db + */ if (timeEntry.getValue() > 0) { eventDB.insertEvent(timeEntry.getValue(), timeEntry.getKey(), datasourceID, f.getId(), null, uniquePath, medDesc, @@ -640,7 +667,7 @@ public class EventsRepository { try { //for each artifact, extract the relevant information for the descriptions insertEventForArtifact(type, blackboardArtifacts.get(i), trans); - update(new ProgressWindow.ProgressUpdate(i, numArtifacts, + update(new ProgressUpdate(i, numArtifacts, Bundle.progressWindow_populatingXevents(type.getDisplayName()))); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "There was a problem inserting event for artifact: " + blackboardArtifacts.get(i).getArtifactID(), ex); // NON-NLS @@ -653,8 +680,10 @@ 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);