From ff7ce09e164505ca30da068734061970053f6cef Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 18 Mar 2016 14:15:31 -0400 Subject: [PATCH 1/7] keep track of db staleness using properties file keep track of db staleness using properties file rather than relying on SleuthKitCase.getLastObjectID() or TimeLineController.getCaseLastArtifactID() --- .../autopsy/timeline/OpenTimelineAction.java | 20 ++- .../timeline/PerCaseTimelineProperties.java | 144 ++++++++++++++++++ .../autopsy/timeline/TimeLineController.java | 128 +++++++++------- .../autopsy/timeline/db/EventDB.java | 89 +---------- .../autopsy/timeline/db/EventsRepository.java | 53 +------ .../autopsy/timeline/ui/StatusBar.java | 5 +- .../timeline/ui/VisualizationPanel.java | 8 +- 7 files changed, 248 insertions(+), 199 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 78b21bd9e8..73d58e49ac 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline; +import java.io.IOException; import java.util.logging.Level; import javax.swing.JOptionPane; import org.openide.awt.ActionID; @@ -31,6 +32,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.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @@ -74,14 +76,18 @@ public class OpenTimelineAction extends CallableSystemAction { return; } - if (timeLineController == null) { - timeLineController = new TimeLineController(currentCase); - } else if (timeLineController.getAutopsyCase() != currentCase) { - timeLineController.closeTimeLine(); - timeLineController = new TimeLineController(currentCase); + try { + if (timeLineController == null) { + timeLineController = new TimeLineController(currentCase); + } else if (timeLineController.getAutopsyCase() != currentCase) { + timeLineController.closeTimeLine(); + timeLineController = new TimeLineController(currentCase); + } + timeLineController.openTimeLine(); + } catch (IOException iOException) { + MessageNotifyUtil.Notify.error("Timeline", "Failed to initialize timeline settings."); + LOGGER.log(Level.SEVERE, "Failed to initialize per case timeline settings."); } - - timeLineController.openTimeLine(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java new file mode 100644 index 0000000000..faeb4e3dac --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java @@ -0,0 +1,144 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 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.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * Provides access to per-case timeline properties/settings. + */ +class PerCaseTimelineProperties { + + public static final String STALE_KEY = "stale"; //NON-NLS + public static final String WAS_INGEST_RUNNING_KEY = "was_ingest_running"; // NON-NLS + + private final Case theCase; + private final Path propertiesPath; + + PerCaseTimelineProperties(Case c) { + this.theCase = c; + propertiesPath = Paths.get(theCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS + } + + public synchronized boolean isDbStale() throws IOException { + String stale = getConfigSetting(STALE_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + } + + public synchronized void setDbStale(Boolean stale) throws IOException { + setConfigSetting(STALE_KEY, stale.toString()); + } + + public synchronized boolean wasIngestRunning() throws IOException { + String stale = getConfigSetting(WAS_INGEST_RUNNING_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + } + + public synchronized void setIngestRunning(Boolean stale) throws IOException { + setConfigSetting(WAS_INGEST_RUNNING_KEY, stale.toString()); + } + + /** + * Makes a new config file of the specified name. Do not include the + * extension. + * + * @param moduleName - The name of the config file to make + * + * @return True if successfully created, false if already exists or an error + * is thrown. + */ + private synchronized Path getPropertiesPath() throws IOException { + + if (!Files.exists(propertiesPath)) { + Path parent = propertiesPath.getParent(); + Files.createDirectories(parent); + Files.createFile(propertiesPath); + } + return propertiesPath; + } + + /** + * Returns the given properties file's setting as specific by settingName. + * + * @param moduleName - The name of the config file to read from. + * @param settingName - The setting name to retrieve. + * + * @return - the value associated with the setting. + * + * @throws IOException + */ + private synchronized String getConfigSetting(String settingName) throws IOException { + return getProperties().getProperty(settingName); + } + + /** + * Sets the given properties file to the given settings. + * + * @param moduleName - The name of the module to be written to. + * @param settingName - The name of the setting to be modified. + * @param settingVal - the value to set the setting to. + */ + private synchronized void setConfigSetting(String settingName, String settingVal) throws IOException { + Path propertiesFile = getPropertiesPath(); + Properties props = getProperties(propertiesFile); + props.setProperty(settingName, settingVal); + + try (OutputStream fos = Files.newOutputStream(propertiesFile)) { + props.store(fos, ""); //NON-NLS + } + } + + /** + * Returns the properties as specified by moduleName. + * + * @param moduleName + * @param propertiesFile the value of propertiesFile + * + * @throws IOException + * @return the java.util.Properties + */ + private synchronized Properties getProperties() throws IOException { + return getProperties(getPropertiesPath()); + } + + /** + * Returns the properties as specified by moduleName. + * + * @param moduleName + * + * @return Properties file as specified by moduleName. + * + * @throws IOException + */ + private synchronized Properties getProperties(final Path propertiesFile) throws IOException { + try (InputStream inputStream = Files.newInputStream(propertiesFile)) { + Properties props = new Properties(); + props.load(inputStream); + return props; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index a33bdb50d1..abfa02a5f3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.timeline; import java.awt.HeadlessException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.time.ZoneId; @@ -33,7 +34,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import javafx.application.Platform; -import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; @@ -49,7 +49,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.concurrent.Worker; -import javafx.scene.control.Dialog; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.swing.SwingUtilities; @@ -139,9 +138,6 @@ public class TimeLineController { private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper(); - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private Dialog currentDialog; - /** * status is a string that will be displayed in the status bar as a kind of * user hint/information when it is not empty @@ -156,6 +152,7 @@ public class TimeLineController { status.set(string); } private final Case autoCase; + private final PerCaseTimelineProperties perCaseTimelineProperties; @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ObservableList quickHideMaskFilters = FXCollections.observableArrayList(); @@ -241,14 +238,8 @@ public class TimeLineController { return selectedTimeRange.getReadOnlyProperty(); } - public ReadOnlyBooleanProperty getNewEventsFlag() { - return newEventsFlag.getReadOnlyProperty(); - } - - private final ReadOnlyBooleanWrapper needsHistogramRebuild = new ReadOnlyBooleanWrapper(false); - - public ReadOnlyBooleanProperty getNeedsHistogramRebuild() { - return needsHistogramRebuild.getReadOnlyProperty(); + public ReadOnlyBooleanProperty eventsDBStaleProperty() { + return eventsDBStale.getReadOnlyProperty(); } synchronized public ReadOnlyBooleanProperty getCanAdvance() { @@ -258,28 +249,26 @@ public class TimeLineController { synchronized public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } - private final ReadOnlyBooleanWrapper newEventsFlag = new ReadOnlyBooleanWrapper(false); + private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(false); private final PromptDialogManager promptDialogManager = new PromptDialogManager(this); - public TimeLineController(Case autoCase) { + public TimeLineController(Case autoCase) throws IOException { this.autoCase = autoCase; - + this.perCaseTimelineProperties = new PerCaseTimelineProperties(autoCase); + eventsDBStale.set(perCaseTimelineProperties.isDbStale()); + eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty()); /* * as the history manager's current state changes, modify the tags * filter to be in sync, and expose that as propery from * TimeLineController. Do we need to do this with datasource or hash hit * filters? */ - historyManager.currentState().addListener(new InvalidationListener() { - public void invalidated(Observable observable) { - ZoomParams historyManagerParams = historyManager.getCurrentState(); - eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter()); - currentParams.set(historyManagerParams); - } + historyManager.currentState().addListener((Observable observable) -> { + ZoomParams historyManagerParams = historyManager.getCurrentState(); + eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter()); + currentParams.set(historyManagerParams); }); - - eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty()); filteredEvents = eventsRepository.getEventsModel(); InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(), @@ -316,19 +305,22 @@ public class TimeLineController { void rebuildRepo() { SwingUtilities.invokeLater(this::closeTimelineWindow); final CancellationProgressTask rebuildRepository = eventsRepository.rebuildRepository(); + boolean ingestRunning = IngestManager.getInstance().isIngestRunning(); 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(); + setIngestRunning(ingestRunning); + switch (newSate) { + case SUCCEEDED: + setEventsDBStale(false); + SwingUtilities.invokeLater(TimeLineController.this::showWindow); + historyManager.reset(filteredEvents.zoomParametersProperty().get()); + TimeLineController.this.showFullRange(); + break; + case FAILED: + case CANCELLED: + setEventsDBStale(true); + break; } }); promptDialogManager.showProgressDialog(rebuildRepository); @@ -409,7 +401,7 @@ public class TimeLineController { /* * if the repo was not rebuilt at minimum rebuild the tags which * may have been updated without our knowing it, since we - * can't/aren't checking them. This should at elast be quick. + * can't/aren't checking them. This should at least be quick. * //TODO: can we check the tags to see if we need to do this? */ if (checkAndPromptForRebuild() == false) { @@ -425,7 +417,7 @@ public class TimeLineController { @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) { + if (eventsRepository.countAllEvents() == 0) { rebuildRepo(); return true; } @@ -451,22 +443,21 @@ public class TimeLineController { private ArrayList getRebuildReasons() { ArrayList rebuildReasons = new ArrayList<>(); //if ingest was running during last rebuild, prompt to rebuild - if (eventsRepository.getWasIngestRunning()) { - 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(Bundle.TimeLineController_rebuildReasons_outOfDate()); + if (perCaseTimelineProperties.wasIngestRunning()) { + rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning()); } - } 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 + + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Error determing the state of the timeline db. We will assume the it 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 events db is stale, prompt to rebuild + if (isEventsDBStale()) { + rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDate()); + } // if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild if (eventsRepository.hasNewColumns() == false) { rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema()); @@ -752,6 +743,38 @@ public class TimeLineController { } } + /** + * is the events db out of date + * + * @return true if the events db is out of date , false otherwise + */ + public boolean isEventsDBStale() { + return eventsDBStale.get(); + } + + /** + * + * @param stale the value of stale + */ + private void setEventsDBStale(final Boolean stale) { + eventsDBStale.set(stale); + try { + perCaseTimelineProperties.setDbStale(stale); + } catch (IOException ex) { + MessageNotifyUtil.Notify.error("Timeline", "Failed to mark the timeline db as " + (stale ? "" : "not ") + "stale. Some results may be out of date or missing."); + LOGGER.log(Level.SEVERE, "Error marking the timeline db as stale.", ex); + } + } + + private void setIngestRunning(boolean ingestRunning) { + try { + perCaseTimelineProperties.setIngestRunning(ingestRunning); + } catch (IOException ex) { + MessageNotifyUtil.Notify.error("Timeline", "Failed to mark the timeline db as populated while ingest was" + (ingestRunning ? "" : "not ") + "running. Some results may be out of date or missing."); + LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); + } + } + private class AutopsyIngestModuleListener implements PropertyChangeListener { @Override @@ -773,15 +796,14 @@ public class TimeLineController { switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { case CONTENT_CHANGED: - case DATA_ADDED: break; + case DATA_ADDED: case FILE_DONE: - Platform.runLater(() -> { - newEventsFlag.set(true); - }); + Platform.runLater(() -> setEventsDBStale(true)); break; } } + } @Immutable @@ -824,7 +846,11 @@ public class TimeLineController { }); break; case DATA_SOURCE_ADDED: - SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen); + Platform.runLater(() -> { + setEventsDBStale(true); + SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen); + }); + break; case CURRENT_CASE: diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index e708fe0b99..ed0357d6fa 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -84,28 +84,6 @@ import org.sqlite.SQLiteJDBCLoader; */ public class EventDB { - /** - * - * enum to represent keys stored in db_info table - */ - private enum DBInfoKey { - - LAST_ARTIFACT_ID("last_artifact_id"), // NON-NLS - LAST_OBJECT_ID("last_object_id"), // NON-NLS - WAS_INGEST_RUNNING("was_ingest_running"); // NON-NLS - - private final String keyName; - - private DBInfoKey(String keyName) { - this.keyName = keyName; - } - - @Override - public String toString() { - return keyName; - } - } - private static final org.sleuthkit.autopsy.coreutils.Logger LOGGER = Logger.getLogger(EventDB.class.getName()); static { @@ -142,14 +120,12 @@ public class EventDB { private final String dbPath; - private PreparedStatement getDBInfoStmt; private PreparedStatement getEventByIDStmt; private PreparedStatement getMaxTimeStmt; private PreparedStatement getMinTimeStmt; private PreparedStatement getDataSourceIDsStmt; private PreparedStatement getHashSetNamesStmt; private PreparedStatement insertRowStmt; - private PreparedStatement recordDBInfoStmt; private PreparedStatement insertHashSetStmt; private PreparedStatement insertHashHitStmt; private PreparedStatement insertTagStmt; @@ -394,14 +370,6 @@ public class EventDB { return resultIDs; } - long getLastArtfactID() { - return getDBInfo(DBInfoKey.LAST_ARTIFACT_ID, -1); - } - - long getLastObjID() { - return getDBInfo(DBInfoKey.LAST_OBJECT_ID, -1); - } - /** * this relies on the fact that no tskObj has ID 0 but 0 is the default * value for the datasource_id column in the events table. @@ -489,10 +457,6 @@ public class EventDB { return -1l; } - boolean getWasIngestRunning() { - return getDBInfo(DBInfoKey.WAS_INGEST_RUNNING, 0) != 0; - } - /** * create the table and indices if they don't already exist * @@ -614,8 +578,6 @@ public class EventDB { getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max FROM events"); // NON-NLS getMinTimeStmt = prepareStatement("SELECT Min(time) AS min FROM events"); // NON-NLS getEventByIDStmt = prepareStatement("SELECT * FROM events WHERE event_id = ?"); // NON-NLS - recordDBInfoStmt = prepareStatement("INSERT OR REPLACE INTO db_info (key, value) values (?, ?)"); // NON-NLS - getDBInfoStmt = prepareStatement("SELECT value FROM db_info WHERE key = ?"); // NON-NLS insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) values (?)"); //NON-NLS selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)"); //NON-NLS @@ -938,18 +900,6 @@ public class EventDB { return eventIDs; } - void recordLastArtifactID(long lastArtfID) { - recordDBInfo(DBInfoKey.LAST_ARTIFACT_ID, lastArtfID); - } - - void recordLastObjID(Long lastObjID) { - recordDBInfo(DBInfoKey.LAST_OBJECT_ID, lastObjID); - } - - void recordWasIngestRunning(boolean wasIngestRunning) { - recordDBInfo(DBInfoKey.WAS_INGEST_RUNNING, (wasIngestRunning ? 1 : 0)); - } - void rollBackTransaction(EventTransaction trans) { trans.rollback(); } @@ -983,8 +933,7 @@ public class EventDB { try { LOGGER.log(Level.INFO, String.format("sqlite-jdbc version %s loaded in %s mode", // NON-NLS - SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode() - ? "native" : "pure-java")); // NON-NLS + SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode() ? "native" : "pure-java")); // NON-NLS } catch (Exception exception) { LOGGER.log(Level.SEVERE, "Failed to determine if sqlite-jdbc is loaded in native or pure-java mode.", exception); //NON-NLS } @@ -1220,28 +1169,6 @@ public class EventDB { return useSubTypes ? "sub_type" : "base_type"; //NON-NLS } - private long getDBInfo(DBInfoKey key, long defaultValue) { - DBLock.lock(); - try { - getDBInfoStmt.setString(1, key.toString()); - - try (ResultSet rs = getDBInfoStmt.executeQuery()) { - long result = defaultValue; - while (rs.next()) { - result = rs.getLong("value"); // NON-NLS - } - return result; - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "failed to read key: " + key + " from db_info", ex); // NON-NLS - } finally { - DBLock.unlock(); - } - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "failed to set key: " + key + " on getDBInfoStmt ", ex); // NON-NLS - } - - return defaultValue; - } private PreparedStatement prepareStatement(String queryString) throws SQLException { PreparedStatement prepareStatement = con.prepareStatement(queryString); @@ -1249,20 +1176,6 @@ public class EventDB { return prepareStatement; } - private void recordDBInfo(DBInfoKey key, long value) { - DBLock.lock(); - try { - recordDBInfoStmt.setString(1, key.toString()); - recordDBInfoStmt.setLong(2, value); - recordDBInfoStmt.executeUpdate(); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "failed to set dbinfo key: " + key + " value: " + value, ex); // NON-NLS - } finally { - DBLock.unlock(); - - } - } - /** * inner class that can reference access database connection */ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 6946ded64d..c8554cfdc3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -54,9 +54,7 @@ import org.sleuthkit.autopsy.casemodule.Case; 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.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; @@ -168,7 +166,7 @@ public class EventsRepository { */ public Long getMaxTime() { return maxCache.getUnchecked("max"); // NON-NLS -// return eventDB.getMaxTime(); + } /** @@ -176,32 +174,9 @@ public class EventsRepository { */ public Long getMinTime() { return minCache.getUnchecked("min"); // NON-NLS -// return eventDB.getMinTime(); + } - private void recordLastArtifactID(long lastArtfID) { - eventDB.recordLastArtifactID(lastArtfID); - } - - private void recordWasIngestRunning(Boolean wasIngestRunning) { - eventDB.recordWasIngestRunning(wasIngestRunning); - } - - private void recordLastObjID(Long lastObjID) { - eventDB.recordLastObjID(lastObjID); - } - - public boolean getWasIngestRunning() { - return eventDB.getWasIngestRunning(); - } - - public Long getLastObjID() { - return eventDB.getLastObjID(); - } - - public long getLastArtfactID() { - return eventDB.getLastArtfactID(); - } public TimeLineEvent getEventById(Long eventID) { return idToEventCache.getUnchecked(eventID); @@ -227,6 +202,10 @@ public class EventsRepository { return eventCountsCache.getUnchecked(params); } + synchronized public int countAllEvents() { + return eventDB.countAllEvents(); + } + private void invalidateCaches() { minCache.invalidateAll(); maxCache.invalidateAll(); @@ -331,17 +310,7 @@ public class EventsRepository { } } - /** - * - * @param lastObjId the value of lastObjId - * @param lastArtfID the value of lastArtfID - * @param injestRunning the value of injestRunning - */ - public void recordDBPopulationState(final long lastObjId, final long lastArtfID, final Boolean injestRunning) { - recordLastObjID(lastObjId); - recordLastArtifactID(lastArtfID); - recordWasIngestRunning(injestRunning); - } + public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) { return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2)); @@ -470,11 +439,6 @@ public class EventsRepository { protected Void call() throws Exception { 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 @@ -513,9 +477,6 @@ public class EventsRepository { Platform.runLater(() -> cancellable.set(false)); restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false); eventDB.commitTransaction(trans); - if (isCancelRequested() == false) { - recordDBPopulationState(lastObjId, lastArtfID, injestRunning); - } eventDB.analyze(); populateFilterData(skCase); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java index 3ff0222beb..eb1ba3091a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java @@ -74,8 +74,8 @@ public class StatusBar extends ToolBar { taskLabel.setVisible(false); HBox.setHgrow(spacer, Priority.ALWAYS); - refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag()); - refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag()); + refreshLabel.visibleProperty().bind(this.controller.eventsDBStaleProperty()); + refreshLabel.managedProperty().bind(this.controller.eventsDBStaleProperty()); taskLabel.textProperty().bind(this.controller.taskTitleProperty()); messageLabel.textProperty().bind(this.controller.taskMessageProperty()); progressBar.progressProperty().bind(this.controller.taskProgressProperty()); @@ -83,6 +83,5 @@ public class StatusBar extends ToolBar { statusLabel.textProperty().bind(this.controller.getStatusProperty()); statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty()); - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index eb3cf09768..2f56b78f79 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -347,9 +347,9 @@ final public class VisualizationPanel extends BorderPane { refreshTimeUI(); //populate the viz //this should use an event(EventBus) , not this weird observable pattern - controller.getNeedsHistogramRebuild().addListener((observable, oldValue, newValue) -> { - if (newValue) { - refreshHistorgram(); + controller.eventsDBStaleProperty().addListener(staleProperty -> { + if (controller.isEventsDBStale()) { + Platform.runLater(VisualizationPanel.this::refreshHistorgram); } }); refreshHistorgram(); @@ -569,7 +569,7 @@ final public class VisualizationPanel extends BorderPane { titledPane.setText(Bundle.NoEventsDialog_titledPane_text()); noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS - + dismissButton.setOnAction(actionEvent -> closeCallback.run()); ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton); From 08b34657604bf9c0bb9a6eaa2971904e89564a77 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 18 Mar 2016 14:22:32 -0400 Subject: [PATCH 2/7] cleanup; remove dead code --- .../autopsy/timeline/OpenTimelineAction.java | 2 +- .../autopsy/timeline/TimeLineController.java | 58 +++++-------------- 2 files changed, 14 insertions(+), 46 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 73d58e49ac..2fed7f5c6a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -80,7 +80,7 @@ public class OpenTimelineAction extends CallableSystemAction { if (timeLineController == null) { timeLineController = new TimeLineController(currentCase); } else if (timeLineController.getAutopsyCase() != currentCase) { - timeLineController.closeTimeLine(); + timeLineController.shutDownTimeLine(); timeLineController = new TimeLineController(currentCase); } timeLineController.openTimeLine(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index abfa02a5f3..b5ca76f38c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2015 Basis Technology Corp. + * Copyright 2014-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,8 +22,6 @@ import java.awt.HeadlessException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; -import java.sql.ResultSet; -import java.sql.SQLException; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; @@ -82,9 +80,6 @@ import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; -import org.sleuthkit.datamodel.TskCoreException; /** * Controller in the MVC design along with model = {@link FilteredEventsModel} @@ -155,10 +150,10 @@ public class TimeLineController { private final PerCaseTimelineProperties perCaseTimelineProperties; @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private final ObservableList quickHideMaskFilters = FXCollections.observableArrayList(); + private final ObservableList quickHideFilters = FXCollections.observableArrayList(); public ObservableList getQuickHideFilters() { - return quickHideMaskFilters; + return quickHideFilters; } /** @@ -216,13 +211,12 @@ public class TimeLineController { @GuardedBy("this") private final ReadOnlyObjectWrapper currentParams = new ReadOnlyObjectWrapper<>(); - //all members should be access with the intrinsict lock of this object held //selected events (ie shown in the result viewer) @GuardedBy("this") private final ObservableList selectedEventIDs = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); /** - * @return an unmodifiable list of the selected event ids + * @return a list of the selected event ids */ synchronized public ObservableList getSelectedEventIDs() { return selectedEventIDs; @@ -249,7 +243,7 @@ public class TimeLineController { synchronized public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } - private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(false); + private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true); private final PromptDialogManager promptDialogManager = new PromptDialogManager(this); @@ -362,7 +356,7 @@ public class TimeLineController { } @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void closeTimeLine() { + public void shutDownTimeLine() { if (mainFrame != null) { listeningToAutopsy = false; IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener); @@ -432,7 +426,6 @@ public class TimeLineController { return false; } - @SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId @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.", @@ -442,8 +435,9 @@ public class TimeLineController { "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 + try { + //if ingest was running during last rebuild, prompt to rebuild if (perCaseTimelineProperties.wasIngestRunning()) { rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning()); } @@ -465,21 +459,6 @@ public class TimeLineController { 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 //NOI18N - try (CaseDbQuery dbQuery = sleuthkitCase.executeQuery(query)) { - ResultSet resultSet = dbQuery.getResultSet(); - while (resultSet.next()) { - caseLastArtfId = resultSet.getLong("max_id"); // NON-NLS //NOI18N - } - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error getting last artifact id: ", ex); // NON-NLS //NOI18N - } - return caseLastArtfId; - } - /** * request a time range the same length as the given period and centered * around the middle of the currently selected range @@ -803,7 +782,6 @@ public class TimeLineController { break; } } - } @Immutable @@ -826,36 +804,26 @@ public class TimeLineController { public void propertyChange(PropertyChangeEvent evt) { switch (Case.Events.valueOf(evt.getPropertyName())) { case BLACKBOARD_ARTIFACT_TAG_ADDED: - executor.submit(() -> { - filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt); - }); + executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt)); break; case BLACKBOARD_ARTIFACT_TAG_DELETED: - executor.submit(() -> { - filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt); - }); + executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt)); break; case CONTENT_TAG_ADDED: - executor.submit(() -> { - filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt); - }); + executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt)); break; case CONTENT_TAG_DELETED: - executor.submit(() -> { - filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt); - }); + executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt)); break; case DATA_SOURCE_ADDED: Platform.runLater(() -> { setEventsDBStale(true); SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen); }); - break; - case CURRENT_CASE: OpenTimelineAction.invalidateController(); - SwingUtilities.invokeLater(TimeLineController.this::closeTimeLine); + SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine); break; } } From fa0fc5f396d55552081aba50e28c624049802ef4 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 18 Mar 2016 15:43:59 -0400 Subject: [PATCH 3/7] cleanup and comments PerCaseTimelineProperties --- .../timeline/PerCaseTimelineProperties.java | 135 +++++++++++------- .../autopsy/timeline/TimeLineController.java | 2 +- 2 files changed, 85 insertions(+), 52 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java index faeb4e3dac..6861dac340 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java @@ -24,52 +24,86 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Objects; import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.casemodule.Case; /** - * Provides access to per-case timeline properties/settings. + * Provides access to per-case timeline properties (key-value store). */ class PerCaseTimelineProperties { - public static final String STALE_KEY = "stale"; //NON-NLS - public static final String WAS_INGEST_RUNNING_KEY = "was_ingest_running"; // NON-NLS + private static final String STALE_KEY = "stale"; //NON-NLS + private static final String WAS_INGEST_RUNNING_KEY = "was_ingest_running"; // NON-NLS - private final Case theCase; + private final Case autoCase; private final Path propertiesPath; PerCaseTimelineProperties(Case c) { - this.theCase = c; - propertiesPath = Paths.get(theCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS - } - - public synchronized boolean isDbStale() throws IOException { - String stale = getConfigSetting(STALE_KEY); - return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); - } - - public synchronized void setDbStale(Boolean stale) throws IOException { - setConfigSetting(STALE_KEY, stale.toString()); - } - - public synchronized boolean wasIngestRunning() throws IOException { - String stale = getConfigSetting(WAS_INGEST_RUNNING_KEY); - return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); - } - - public synchronized void setIngestRunning(Boolean stale) throws IOException { - setConfigSetting(WAS_INGEST_RUNNING_KEY, stale.toString()); + Objects.requireNonNull(c, "Case must not be null"); + this.autoCase = c; + propertiesPath = Paths.get(autoCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS } /** - * Makes a new config file of the specified name. Do not include the - * extension. + * Is the DB stale, i.e. does it need to be updated because new datasources + * (eg) have been added to the case. * - * @param moduleName - The name of the config file to make + * @return true if the db is stale * - * @return True if successfully created, false if already exists or an error - * is thrown. + * @throws IOException if there is a problem reading the state from disk + */ + public synchronized boolean isDBStale() throws IOException { + + String stale = getProperty(STALE_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + + } + + /** + * record the state of the events db as stale(true) or not stale(false). + * + * @param stale the new state of the event db. true for stale, false for not + * stale. + * + * @throws IOException if there was a problem writing the state to disk. + */ + public synchronized void setDbStale(Boolean stale) throws IOException { + setProperty(STALE_KEY, stale.toString()); + } + + /** + * Was ingest running the last time the database was updated? + * + * @return true if ingest was running the last time the db was updated + * + * @throws IOException if there was a problem reading from disk + */ + public synchronized boolean wasIngestRunning() throws IOException { + String stale = getProperty(WAS_INGEST_RUNNING_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + } + + /** + * record whether ingest was running during the last time the database was + * updated + * + * @param ingestRunning true if ingest was running + * + * @throws IOException if there was a problem writing to disk + */ + public synchronized void setIngestRunning(Boolean ingestRunning) throws IOException { + setProperty(WAS_INGEST_RUNNING_KEY, ingestRunning.toString()); + } + + /** + * Get a {@link Path} to the properties file. If the file does not exist, it + * will be created. + * + * @return the Path to the properties file. + * + * @throws IOException if there was a problem creating the properties file */ private synchronized Path getPropertiesPath() throws IOException { @@ -82,30 +116,30 @@ class PerCaseTimelineProperties { } /** - * Returns the given properties file's setting as specific by settingName. + * Returns the property with the given key. * - * @param moduleName - The name of the config file to read from. - * @param settingName - The setting name to retrieve. + * @param propertyKey - The property key to get the value for. * - * @return - the value associated with the setting. + * @return - the value associated with the property. * - * @throws IOException + * @throws IOException if there was a problem reading the property from disk */ - private synchronized String getConfigSetting(String settingName) throws IOException { - return getProperties().getProperty(settingName); + private synchronized String getProperty(String propertyKey) throws IOException { + return getProperties().getProperty(propertyKey); } /** - * Sets the given properties file to the given settings. + * Sets the given property to the given value. * - * @param moduleName - The name of the module to be written to. - * @param settingName - The name of the setting to be modified. - * @param settingVal - the value to set the setting to. + * @param propertyKey - The key of the property to be modified. + * @param propertyValue - the value to set the property to. + * + * @throws IOException if there was a problem writing the property to disk */ - private synchronized void setConfigSetting(String settingName, String settingVal) throws IOException { + private synchronized void setProperty(String propertyKey, String propertyValue) throws IOException { Path propertiesFile = getPropertiesPath(); Properties props = getProperties(propertiesFile); - props.setProperty(settingName, settingVal); + props.setProperty(propertyKey, propertyValue); try (OutputStream fos = Files.newOutputStream(propertiesFile)) { props.store(fos, ""); //NON-NLS @@ -113,26 +147,25 @@ class PerCaseTimelineProperties { } /** - * Returns the properties as specified by moduleName. + * Get a {@link Properties} object used to store the timeline properties. * - * @param moduleName - * @param propertiesFile the value of propertiesFile + * @return a properties object * - * @throws IOException - * @return the java.util.Properties + * @throws IOException if there was a problem reading the .properties file */ private synchronized Properties getProperties() throws IOException { return getProperties(getPropertiesPath()); } /** - * Returns the properties as specified by moduleName. + * Gets a {@link Properties} object populated form the given .properties + * file. * - * @param moduleName + * @param propertiesFile a path to the .properties file to load * - * @return Properties file as specified by moduleName. + * @return a properties object * - * @throws IOException + * @throws IOException if there was a problem reading the .properties file */ private synchronized Properties getProperties(final Path propertiesFile) throws IOException { try (InputStream inputStream = Files.newInputStream(propertiesFile)) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index b5ca76f38c..ee581fbd7d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -250,7 +250,7 @@ public class TimeLineController { public TimeLineController(Case autoCase) throws IOException { this.autoCase = autoCase; this.perCaseTimelineProperties = new PerCaseTimelineProperties(autoCase); - eventsDBStale.set(perCaseTimelineProperties.isDbStale()); + eventsDBStale.set(perCaseTimelineProperties.isDBStale()); eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty()); /* * as the history manager's current state changes, modify the tags From 1e6673bbeefe2f076bafbe705e922596c9d364fa Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 18 Mar 2016 16:25:27 -0400 Subject: [PATCH 4/7] cleanup and internationalize OpenTimelineAction --- .../autopsy/timeline/Bundle.properties | 4 +- .../autopsy/timeline/OpenTimelineAction.java | 50 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties index 9f2e0c950c..131934652e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties @@ -1,4 +1,4 @@ -CTL_MakeTimeline="Timeline" +CTL_MakeTimeline=Timeline CTL_TimeLineTopComponentAction=TimeLineTopComponent CTL_TimeLineTopComponent=Timeline Window HINT_TimeLineTopComponent=This is a Timeline window @@ -24,7 +24,5 @@ TimelinePanel.jButton7.text=3d TimelinePanel.jButton2.text=1m TimelinePanel.jButton3.text=3m TimelinePanel.jButton4.text=2w -OpenTimelineAction.title=Timeline -OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources. ProgressWindow.progressHeader.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 2fed7f5c6a..b766394926 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline; import java.io.IOException; import java.util.logging.Level; -import javax.swing.JOptionPane; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; @@ -28,7 +27,6 @@ import org.openide.awt.ActionRegistration; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; -import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.coreutils.Logger; @@ -60,39 +58,39 @@ public class OpenTimelineAction extends CallableSystemAction { return Case.isCaseOpen() && fxInited;// && Case.getCurrentCase().hasData(); } + @NbBundle.Messages({ + "OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.", + "OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."}) @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void performAction() { - //check case - if (!Case.isCaseOpen()) { - return; - } - final Case currentCase = Case.getCurrentCase(); - - if (currentCase.hasData() == false) { - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(this.getClass(), "OpenTimeLineAction.msgdlg.text")); - LOGGER.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS - return; - } - try { - if (timeLineController == null) { - timeLineController = new TimeLineController(currentCase); - } else if (timeLineController.getAutopsyCase() != currentCase) { - timeLineController.shutDownTimeLine(); - timeLineController = new TimeLineController(currentCase); + Case currentCase = Case.getCurrentCase(); + if (currentCase.hasData() == false) { + MessageNotifyUtil.Message.info(Bundle.OpenTimeLineAction_msgdlg_text()); + LOGGER.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS + return; } - timeLineController.openTimeLine(); - } catch (IOException iOException) { - MessageNotifyUtil.Notify.error("Timeline", "Failed to initialize timeline settings."); - LOGGER.log(Level.SEVERE, "Failed to initialize per case timeline settings."); + try { + if (timeLineController == null) { + timeLineController = new TimeLineController(currentCase); + } else if (timeLineController.getAutopsyCase() != currentCase) { + timeLineController.shutDownTimeLine(); + timeLineController = new TimeLineController(currentCase); + } + timeLineController.openTimeLine(); + } catch (IOException iOException) { + MessageNotifyUtil.Message.error(Bundle.OpenTimelineAction_settingsErrorMessage()); + LOGGER.log(Level.SEVERE, "Failed to initialize per case timeline settings.", iOException); + } + } catch (IllegalStateException e) { + //there is no case... Do nothing. } } @Override public String getName() { - return NbBundle.getMessage(TimeLineTopComponent.class, "OpenTimelineAction.title"); + return NbBundle.getMessage(OpenTimelineAction.class, "CTL_MakeTimeline"); } @Override From ea212341fcc37add4a1818920d5fc74acc4b23c1 Mon Sep 17 00:00:00 2001 From: Oliver Spohngellert Date: Mon, 21 Mar 2016 13:57:57 -0400 Subject: [PATCH 5/7] Added better error message --- Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index fed97afb85..6929666658 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -684,7 +684,7 @@ class ReportGenerator { } catch (InterruptedException | ExecutionException ex) { MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), + NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getCause().getLocalizedMessage(), MessageNotifyUtil.MessageType.ERROR); logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS } // catch and ignore if we were cancelled From 3c298c5967eedbce6c67e66350f5132f00e779a6 Mon Sep 17 00:00:00 2001 From: Oliver Spohngellert Date: Mon, 21 Mar 2016 14:27:46 -0400 Subject: [PATCH 6/7] Fixed error message display in table reports worker --- Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 6929666658..69e2839e6e 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -684,9 +684,10 @@ class ReportGenerator { } catch (InterruptedException | ExecutionException ex) { MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getCause().getLocalizedMessage(), + NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), MessageNotifyUtil.MessageType.ERROR); logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS + logger.log(Level.SEVERE, "failed to generate reports", ex.getCause()); //NON-NLS } // catch and ignore if we were cancelled catch (java.util.concurrent.CancellationException ex) { } finally { From 68e4f094a3b939c05844b15b7382fd9b1344ff41 Mon Sep 17 00:00:00 2001 From: Oliver Spohngellert Date: Mon, 21 Mar 2016 15:07:26 -0400 Subject: [PATCH 7/7] Switched error messages --- Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 69e2839e6e..38a5931427 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -686,8 +686,8 @@ class ReportGenerator { NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), MessageNotifyUtil.MessageType.ERROR); - logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS logger.log(Level.SEVERE, "failed to generate reports", ex.getCause()); //NON-NLS + logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS } // catch and ignore if we were cancelled catch (java.util.concurrent.CancellationException ex) { } finally {