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; 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;
}
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.
} }
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;
}
if (timeLineController == null) {
timeLineController = new TimeLineController(currentCase);
} else if (timeLineController.getAutopsyCase() != currentCase) {
timeLineController.closeTimeLine();
timeLineController = new TimeLineController(currentCase);
}
timeLineController.openTimeLine();
} }
@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());
TimeLineController.this.showFullRange();
historyManager.reset(filteredEvents.zoomParametersProperty().get());
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<>();
//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 { try {
//if the last artifact and object ids don't match between skc and tldb, prompt to rebuild //if ingest was running during last rebuild, prompt to rebuild
if (sleuthkitCase.getLastObjectId() != eventsRepository.getLastObjID() if (perCaseTimelineProperties.wasIngestRunning()) {
|| getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) { rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_ingestWasRunning());
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 } 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(), 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:
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen); Platform.runLater(() -> {
setEventsDBStale(true);
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();