Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop

This commit is contained in:
Richard Cordovano 2016-03-21 17:26:26 -04:00
commit 9d97ebdf95
9 changed files with 308 additions and 261 deletions

View File

@ -686,6 +686,7 @@ class ReportGenerator {
NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"),
NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(),
MessageNotifyUtil.MessageType.ERROR); MessageNotifyUtil.MessageType.ERROR);
logger.log(Level.SEVERE, "failed to generate reports", ex.getCause()); //NON-NLS
logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS
} // catch and ignore if we were cancelled } // catch and ignore if we were cancelled
catch (java.util.concurrent.CancellationException ex) { catch (java.util.concurrent.CancellationException ex) {

View File

@ -1,4 +1,4 @@
CTL_MakeTimeline="Timeline" CTL_MakeTimeline=Timeline
CTL_TimeLineTopComponentAction=TimeLineTopComponent CTL_TimeLineTopComponentAction=TimeLineTopComponent
CTL_TimeLineTopComponent=Timeline Window CTL_TimeLineTopComponent=Timeline Window
HINT_TimeLineTopComponent=This is a Timeline window HINT_TimeLineTopComponent=This is a Timeline window
@ -24,7 +24,5 @@ TimelinePanel.jButton7.text=3d
TimelinePanel.jButton2.text=1m TimelinePanel.jButton2.text=1m
TimelinePanel.jButton3.text=3m TimelinePanel.jButton3.text=3m
TimelinePanel.jButton4.text=2w TimelinePanel.jButton4.text=2w
OpenTimelineAction.title=Timeline
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
ProgressWindow.progressHeader.text=\ ProgressWindow.progressHeader.text=\

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,8 +18,8 @@
*/ */
package org.sleuthkit.autopsy.timeline; package org.sleuthkit.autopsy.timeline;
import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JOptionPane;
import org.openide.awt.ActionID; import org.openide.awt.ActionID;
import org.openide.awt.ActionReference; import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences; import org.openide.awt.ActionReferences;
@ -27,10 +27,10 @@ import org.openide.awt.ActionRegistration;
import org.openide.util.HelpCtx; import org.openide.util.HelpCtx;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
@ -58,35 +58,39 @@ public class OpenTimelineAction extends CallableSystemAction {
return Case.isCaseOpen() && fxInited;// && Case.getCurrentCase().hasData(); 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 @Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void performAction() { public void performAction() {
//check case try {
if (!Case.isCaseOpen()) { Case currentCase = Case.getCurrentCase();
return;
}
final Case currentCase = Case.getCurrentCase();
if (currentCase.hasData() == false) { if (currentCase.hasData() == false) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), MessageNotifyUtil.Message.info(Bundle.OpenTimeLineAction_msgdlg_text());
NbBundle.getMessage(this.getClass(), "OpenTimeLineAction.msgdlg.text"));
LOGGER.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS LOGGER.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS
return; return;
} }
try {
if (timeLineController == null) { if (timeLineController == null) {
timeLineController = new TimeLineController(currentCase); timeLineController = new TimeLineController(currentCase);
} else if (timeLineController.getAutopsyCase() != currentCase) { } else if (timeLineController.getAutopsyCase() != currentCase) {
timeLineController.closeTimeLine(); timeLineController.shutDownTimeLine();
timeLineController = new TimeLineController(currentCase); timeLineController = new TimeLineController(currentCase);
} }
timeLineController.openTimeLine(); 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 @Override
public String getName() { public String getName() {
return NbBundle.getMessage(TimeLineTopComponent.class, "OpenTimelineAction.title"); return NbBundle.getMessage(OpenTimelineAction.class, "CTL_MakeTimeline");
} }
@Override @Override

View File

@ -0,0 +1,177 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline;
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.Objects;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.casemodule.Case;
/**
* Provides access to per-case timeline properties (key-value store).
*/
class PerCaseTimelineProperties {
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 autoCase;
private final Path propertiesPath;
PerCaseTimelineProperties(Case c) {
Objects.requireNonNull(c, "Case must not be null");
this.autoCase = c;
propertiesPath = Paths.get(autoCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS
}
/**
* Is the DB stale, i.e. does it need to be updated because new datasources
* (eg) have been added to the case.
*
* @return true if the db is stale
*
* @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 {
if (!Files.exists(propertiesPath)) {
Path parent = propertiesPath.getParent();
Files.createDirectories(parent);
Files.createFile(propertiesPath);
}
return propertiesPath;
}
/**
* Returns the property with the given key.
*
* @param propertyKey - The property key to get the value for.
*
* @return - the value associated with the property.
*
* @throws IOException if there was a problem reading the property from disk
*/
private synchronized String getProperty(String propertyKey) throws IOException {
return getProperties().getProperty(propertyKey);
}
/**
* Sets the given property to the given value.
*
* @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 setProperty(String propertyKey, String propertyValue) throws IOException {
Path propertiesFile = getPropertiesPath();
Properties props = getProperties(propertiesFile);
props.setProperty(propertyKey, propertyValue);
try (OutputStream fos = Files.newOutputStream(propertiesFile)) {
props.store(fos, ""); //NON-NLS
}
}
/**
* Get a {@link Properties} object used to store the timeline properties.
*
* @return a properties object
*
* @throws IOException if there was a problem reading the .properties file
*/
private synchronized Properties getProperties() throws IOException {
return getProperties(getPropertiesPath());
}
/**
* Gets a {@link Properties} object populated form the given .properties
* file.
*
* @param propertiesFile a path to the .properties file to load
*
* @return a properties object
*
* @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)) {
Properties props = new Properties();
props.load(inputStream);
return props;
}
}
}

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2014-2015 Basis Technology Corp. * Copyright 2014-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,8 +21,7 @@ package org.sleuthkit.autopsy.timeline;
import java.awt.HeadlessException; import java.awt.HeadlessException;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.sql.ResultSet; import java.io.IOException;
import java.sql.SQLException;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -33,7 +32,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyBooleanWrapper;
@ -49,7 +47,6 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.concurrent.Worker; import javafx.concurrent.Worker;
import javafx.scene.control.Dialog;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@ -83,9 +80,6 @@ import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; 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} * Controller in the MVC design along with model = {@link FilteredEventsModel}
@ -139,9 +133,6 @@ public class TimeLineController {
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper(); 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 * status is a string that will be displayed in the status bar as a kind of
* user hint/information when it is not empty * user hint/information when it is not empty
@ -156,12 +147,13 @@ public class TimeLineController {
status.set(string); status.set(string);
} }
private final Case autoCase; private final Case autoCase;
private final PerCaseTimelineProperties perCaseTimelineProperties;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ObservableList<DescriptionFilter> quickHideMaskFilters = FXCollections.observableArrayList(); private final ObservableList<DescriptionFilter> quickHideFilters = FXCollections.observableArrayList();
public ObservableList<DescriptionFilter> getQuickHideFilters() { public ObservableList<DescriptionFilter> getQuickHideFilters() {
return quickHideMaskFilters; return quickHideFilters;
} }
/** /**
@ -219,13 +211,12 @@ public class TimeLineController {
@GuardedBy("this") @GuardedBy("this")
private final ReadOnlyObjectWrapper<ZoomParams> currentParams = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<ZoomParams> currentParams = new ReadOnlyObjectWrapper<>();
//all members should be access with the intrinsict lock of this object held
//selected events (ie shown in the result viewer) //selected events (ie shown in the result viewer)
@GuardedBy("this") @GuardedBy("this")
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList()); private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList());
/** /**
* @return an unmodifiable list of the selected event ids * @return a list of the selected event ids
*/ */
synchronized public ObservableList<Long> getSelectedEventIDs() { synchronized public ObservableList<Long> getSelectedEventIDs() {
return selectedEventIDs; return selectedEventIDs;
@ -241,14 +232,8 @@ public class TimeLineController {
return selectedTimeRange.getReadOnlyProperty(); return selectedTimeRange.getReadOnlyProperty();
} }
public ReadOnlyBooleanProperty getNewEventsFlag() { public ReadOnlyBooleanProperty eventsDBStaleProperty() {
return newEventsFlag.getReadOnlyProperty(); return eventsDBStale.getReadOnlyProperty();
}
private final ReadOnlyBooleanWrapper needsHistogramRebuild = new ReadOnlyBooleanWrapper(false);
public ReadOnlyBooleanProperty getNeedsHistogramRebuild() {
return needsHistogramRebuild.getReadOnlyProperty();
} }
synchronized public ReadOnlyBooleanProperty getCanAdvance() { synchronized public ReadOnlyBooleanProperty getCanAdvance() {
@ -258,28 +243,26 @@ public class TimeLineController {
synchronized public ReadOnlyBooleanProperty getCanRetreat() { synchronized public ReadOnlyBooleanProperty getCanRetreat() {
return historyManager.getCanRetreat(); return historyManager.getCanRetreat();
} }
private final ReadOnlyBooleanWrapper newEventsFlag = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
private final PromptDialogManager promptDialogManager = new PromptDialogManager(this); private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
public TimeLineController(Case autoCase) { public TimeLineController(Case autoCase) throws IOException {
this.autoCase = autoCase; 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 * as the history manager's current state changes, modify the tags
* filter to be in sync, and expose that as propery from * filter to be in sync, and expose that as propery from
* TimeLineController. Do we need to do this with datasource or hash hit * TimeLineController. Do we need to do this with datasource or hash hit
* filters? * filters?
*/ */
historyManager.currentState().addListener(new InvalidationListener() { historyManager.currentState().addListener((Observable observable) -> {
public void invalidated(Observable observable) {
ZoomParams historyManagerParams = historyManager.getCurrentState(); ZoomParams historyManagerParams = historyManager.getCurrentState();
eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter()); eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter());
currentParams.set(historyManagerParams); currentParams.set(historyManagerParams);
}
}); });
eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty());
filteredEvents = eventsRepository.getEventsModel(); filteredEvents = eventsRepository.getEventsModel();
InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(), InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
@ -316,19 +299,22 @@ public class TimeLineController {
void rebuildRepo() { void rebuildRepo() {
SwingUtilities.invokeLater(this::closeTimelineWindow); SwingUtilities.invokeLater(this::closeTimelineWindow);
final CancellationProgressTask<?> rebuildRepository = eventsRepository.rebuildRepository(); final CancellationProgressTask<?> rebuildRepository = eventsRepository.rebuildRepository();
boolean ingestRunning = IngestManager.getInstance().isIngestRunning();
rebuildRepository.stateProperty().addListener((stateProperty, oldState, newSate) -> { rebuildRepository.stateProperty().addListener((stateProperty, oldState, newSate) -> {
//this will be on JFX thread //this will be on JFX thread
if (newSate == Worker.State.SUCCEEDED) { setIngestRunning(ingestRunning);
//TODO: this looks hacky. what is going on? should this be an event? switch (newSate) {
needsHistogramRebuild.set(true); case SUCCEEDED:
needsHistogramRebuild.set(false); setEventsDBStale(false);
SwingUtilities.invokeLater(TimeLineController.this::showWindow); SwingUtilities.invokeLater(TimeLineController.this::showWindow);
//TODO: should this be an event?
newEventsFlag.set(false);
historyManager.reset(filteredEvents.zoomParametersProperty().get()); historyManager.reset(filteredEvents.zoomParametersProperty().get());
TimeLineController.this.showFullRange(); TimeLineController.this.showFullRange();
break;
case FAILED:
case CANCELLED:
setEventsDBStale(true);
break;
} }
}); });
promptDialogManager.showProgressDialog(rebuildRepository); promptDialogManager.showProgressDialog(rebuildRepository);
@ -370,7 +356,7 @@ public class TimeLineController {
} }
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void closeTimeLine() { public void shutDownTimeLine() {
if (mainFrame != null) { if (mainFrame != null) {
listeningToAutopsy = false; listeningToAutopsy = false;
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener); IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
@ -409,7 +395,7 @@ public class TimeLineController {
/* /*
* if the repo was not rebuilt at minimum rebuild the tags which * if the repo was not rebuilt at minimum rebuild the tags which
* may have been updated without our knowing it, since we * 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? * //TODO: can we check the tags to see if we need to do this?
*/ */
if (checkAndPromptForRebuild() == false) { if (checkAndPromptForRebuild() == false) {
@ -425,7 +411,7 @@ public class TimeLineController {
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private boolean checkAndPromptForRebuild() { private boolean checkAndPromptForRebuild() {
//if the repo is empty just (r)ebuild it with out asking, they can always cancel part way through; //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(); rebuildRepo();
return true; return true;
} }
@ -440,7 +426,6 @@ public class TimeLineController {
return false; return false;
} }
@SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId
@ThreadConfined(type = ThreadConfined.ThreadType.ANY) @ThreadConfined(type = ThreadConfined.ThreadType.ANY)
@NbBundle.Messages({"TimeLineController.errorTitle=Timeline error.", @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.outOfDate.errorMessage=Error determing if the timeline is out of date. We will assume it should be updated. See the logs for more details.",
@ -450,23 +435,23 @@ 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."}) "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<String> getRebuildReasons() { private ArrayList<String> getRebuildReasons() {
ArrayList<String> rebuildReasons = new ArrayList<>(); ArrayList<String> rebuildReasons = new ArrayList<>();
try {
//if ingest was running during last rebuild, prompt to rebuild //if ingest was running during last rebuild, prompt to rebuild
if (eventsRepository.getWasIngestRunning()) { if (perCaseTimelineProperties.wasIngestRunning()) {
rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning()); rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning());
} }
final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase();
try { } catch (IOException ex) {
//if the last artifact and object ids don't match between skc and tldb, prompt to rebuild LOGGER.log(Level.SEVERE, "Error determing the state of the timeline db. We will assume the it is out of date.", ex); // NON-NLS
if (sleuthkitCase.getLastObjectId() != eventsRepository.getLastObjID()
|| getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) {
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(Bundle.TimeLineController_errorTitle(), MessageNotifyUtil.Notify.error(Bundle.TimeLineController_errorTitle(),
Bundle.TimeLineController_outOfDate_errorMessage()); Bundle.TimeLineController_outOfDate_errorMessage());
rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError()); 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 the TLDB schema has been upgraded since last time TL ran, prompt for rebuild
if (eventsRepository.hasNewColumns() == false) { if (eventsRepository.hasNewColumns() == false) {
rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema()); rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_incompleteOldSchema());
@ -474,21 +459,6 @@ public class TimeLineController {
return rebuildReasons; 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 * request a time range the same length as the given period and centered
* around the middle of the currently selected range * around the middle of the currently selected range
@ -752,6 +722,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 { private class AutopsyIngestModuleListener implements PropertyChangeListener {
@Override @Override
@ -773,12 +775,10 @@ public class TimeLineController {
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
case CONTENT_CHANGED: case CONTENT_CHANGED:
case DATA_ADDED:
break; break;
case DATA_ADDED:
case FILE_DONE: case FILE_DONE:
Platform.runLater(() -> { Platform.runLater(() -> setEventsDBStale(true));
newEventsFlag.set(true);
});
break; break;
} }
} }
@ -804,32 +804,26 @@ public class TimeLineController {
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
switch (Case.Events.valueOf(evt.getPropertyName())) { switch (Case.Events.valueOf(evt.getPropertyName())) {
case BLACKBOARD_ARTIFACT_TAG_ADDED: case BLACKBOARD_ARTIFACT_TAG_ADDED:
executor.submit(() -> { executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt));
filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt);
});
break; break;
case BLACKBOARD_ARTIFACT_TAG_DELETED: case BLACKBOARD_ARTIFACT_TAG_DELETED:
executor.submit(() -> { executor.submit(() -> filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt));
filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt);
});
break; break;
case CONTENT_TAG_ADDED: case CONTENT_TAG_ADDED:
executor.submit(() -> { executor.submit(() -> filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt));
filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt);
});
break; break;
case CONTENT_TAG_DELETED: case CONTENT_TAG_DELETED:
executor.submit(() -> { executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt));
filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt);
});
break; break;
case DATA_SOURCE_ADDED: case DATA_SOURCE_ADDED:
Platform.runLater(() -> {
setEventsDBStale(true);
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen); SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen);
});
break; break;
case CURRENT_CASE: case CURRENT_CASE:
OpenTimelineAction.invalidateController(); OpenTimelineAction.invalidateController();
SwingUtilities.invokeLater(TimeLineController.this::closeTimeLine); SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
break; break;
} }
} }

View File

@ -84,28 +84,6 @@ import org.sqlite.SQLiteJDBCLoader;
*/ */
public class EventDB { 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()); private static final org.sleuthkit.autopsy.coreutils.Logger LOGGER = Logger.getLogger(EventDB.class.getName());
static { static {
@ -142,14 +120,12 @@ public class EventDB {
private final String dbPath; private final String dbPath;
private PreparedStatement getDBInfoStmt;
private PreparedStatement getEventByIDStmt; private PreparedStatement getEventByIDStmt;
private PreparedStatement getMaxTimeStmt; private PreparedStatement getMaxTimeStmt;
private PreparedStatement getMinTimeStmt; private PreparedStatement getMinTimeStmt;
private PreparedStatement getDataSourceIDsStmt; private PreparedStatement getDataSourceIDsStmt;
private PreparedStatement getHashSetNamesStmt; private PreparedStatement getHashSetNamesStmt;
private PreparedStatement insertRowStmt; private PreparedStatement insertRowStmt;
private PreparedStatement recordDBInfoStmt;
private PreparedStatement insertHashSetStmt; private PreparedStatement insertHashSetStmt;
private PreparedStatement insertHashHitStmt; private PreparedStatement insertHashHitStmt;
private PreparedStatement insertTagStmt; private PreparedStatement insertTagStmt;
@ -394,14 +370,6 @@ public class EventDB {
return resultIDs; 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 * 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. * value for the datasource_id column in the events table.
@ -489,10 +457,6 @@ public class EventDB {
return -1l; return -1l;
} }
boolean getWasIngestRunning() {
return getDBInfo(DBInfoKey.WAS_INGEST_RUNNING, 0) != 0;
}
/** /**
* create the table and indices if they don't already exist * 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 getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max FROM events"); // NON-NLS
getMinTimeStmt = prepareStatement("SELECT Min(time) AS min 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 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 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 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 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; 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) { void rollBackTransaction(EventTransaction trans) {
trans.rollback(); trans.rollback();
} }
@ -983,8 +933,7 @@ public class EventDB {
try { try {
LOGGER.log(Level.INFO, String.format("sqlite-jdbc version %s loaded in %s mode", // NON-NLS LOGGER.log(Level.INFO, String.format("sqlite-jdbc version %s loaded in %s mode", // NON-NLS
SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode() SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode() ? "native" : "pure-java")); // NON-NLS
? "native" : "pure-java")); // NON-NLS
} catch (Exception exception) { } catch (Exception exception) {
LOGGER.log(Level.SEVERE, "Failed to determine if sqlite-jdbc is loaded in native or pure-java mode.", exception); //NON-NLS 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 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 { private PreparedStatement prepareStatement(String queryString) throws SQLException {
PreparedStatement prepareStatement = con.prepareStatement(queryString); PreparedStatement prepareStatement = con.prepareStatement(queryString);
@ -1249,20 +1176,6 @@ public class EventDB {
return prepareStatement; 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 * inner class that can reference access database connection
*/ */

View File

@ -54,9 +54,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.timeline.CancellationProgressTask; 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.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
@ -168,7 +166,7 @@ public class EventsRepository {
*/ */
public Long getMaxTime() { public Long getMaxTime() {
return maxCache.getUnchecked("max"); // NON-NLS return maxCache.getUnchecked("max"); // NON-NLS
// return eventDB.getMaxTime();
} }
/** /**
@ -176,32 +174,9 @@ public class EventsRepository {
*/ */
public Long getMinTime() { public Long getMinTime() {
return minCache.getUnchecked("min"); // NON-NLS 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) { public TimeLineEvent getEventById(Long eventID) {
return idToEventCache.getUnchecked(eventID); return idToEventCache.getUnchecked(eventID);
@ -227,6 +202,10 @@ public class EventsRepository {
return eventCountsCache.getUnchecked(params); return eventCountsCache.getUnchecked(params);
} }
synchronized public int countAllEvents() {
return eventDB.countAllEvents();
}
private void invalidateCaches() { private void invalidateCaches() {
minCache.invalidateAll(); minCache.invalidateAll();
maxCache.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) { public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) {
return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2)); return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2));
@ -470,11 +439,6 @@ public class EventsRepository {
protected Void call() throws Exception { protected Void call() throws Exception {
EventDB.EventTransaction trans = null; 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) { if (dbPopulationMode == DBPopulationMode.FULL) {
//drop old db, and add back MAC and artifact events //drop old db, and add back MAC and artifact events
LOGGER.log(Level.INFO, "Beginning population of timeline db."); // NON-NLS LOGGER.log(Level.INFO, "Beginning population of timeline db."); // NON-NLS
@ -513,9 +477,6 @@ public class EventsRepository {
Platform.runLater(() -> cancellable.set(false)); Platform.runLater(() -> cancellable.set(false));
restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false); restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false);
eventDB.commitTransaction(trans); eventDB.commitTransaction(trans);
if (isCancelRequested() == false) {
recordDBPopulationState(lastObjId, lastArtfID, injestRunning);
}
eventDB.analyze(); eventDB.analyze();
populateFilterData(skCase); populateFilterData(skCase);

View File

@ -74,8 +74,8 @@ public class StatusBar extends ToolBar {
taskLabel.setVisible(false); taskLabel.setVisible(false);
HBox.setHgrow(spacer, Priority.ALWAYS); HBox.setHgrow(spacer, Priority.ALWAYS);
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag()); refreshLabel.visibleProperty().bind(this.controller.eventsDBStaleProperty());
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag()); refreshLabel.managedProperty().bind(this.controller.eventsDBStaleProperty());
taskLabel.textProperty().bind(this.controller.taskTitleProperty()); taskLabel.textProperty().bind(this.controller.taskTitleProperty());
messageLabel.textProperty().bind(this.controller.taskMessageProperty()); messageLabel.textProperty().bind(this.controller.taskMessageProperty());
progressBar.progressProperty().bind(this.controller.taskProgressProperty()); progressBar.progressProperty().bind(this.controller.taskProgressProperty());
@ -83,6 +83,5 @@ public class StatusBar extends ToolBar {
statusLabel.textProperty().bind(this.controller.getStatusProperty()); statusLabel.textProperty().bind(this.controller.getStatusProperty());
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty()); statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
} }
} }

View File

@ -347,9 +347,9 @@ final public class VisualizationPanel extends BorderPane {
refreshTimeUI(); //populate the viz refreshTimeUI(); //populate the viz
//this should use an event(EventBus) , not this weird observable pattern //this should use an event(EventBus) , not this weird observable pattern
controller.getNeedsHistogramRebuild().addListener((observable, oldValue, newValue) -> { controller.eventsDBStaleProperty().addListener(staleProperty -> {
if (newValue) { if (controller.isEventsDBStale()) {
refreshHistorgram(); Platform.runLater(VisualizationPanel.this::refreshHistorgram);
} }
}); });
refreshHistorgram(); refreshHistorgram();