mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-19 11:07:43 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop
This commit is contained in:
commit
9d97ebdf95
@ -686,6 +686,7 @@ 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.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) {
|
||||
|
@ -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=\
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,8 +18,8 @@
|
||||
*/
|
||||
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;
|
||||
@ -27,10 +27,10 @@ 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;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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;
|
||||
try {
|
||||
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;
|
||||
}
|
||||
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
|
||||
public String getName() {
|
||||
return NbBundle.getMessage(TimeLineTopComponent.class, "OpenTimelineAction.title");
|
||||
return NbBundle.getMessage(OpenTimelineAction.class, "CTL_MakeTimeline");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-2015 Basis Technology Corp.
|
||||
* Copyright 2014-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -33,7 +32,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 +47,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;
|
||||
@ -83,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}
|
||||
@ -139,9 +133,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,12 +147,13 @@ public class TimeLineController {
|
||||
status.set(string);
|
||||
}
|
||||
private final Case autoCase;
|
||||
private final PerCaseTimelineProperties perCaseTimelineProperties;
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private final ObservableList<DescriptionFilter> quickHideMaskFilters = FXCollections.observableArrayList();
|
||||
private final ObservableList<DescriptionFilter> quickHideFilters = FXCollections.observableArrayList();
|
||||
|
||||
public ObservableList<DescriptionFilter> getQuickHideFilters() {
|
||||
return quickHideMaskFilters;
|
||||
return quickHideFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,13 +211,12 @@ public class TimeLineController {
|
||||
@GuardedBy("this")
|
||||
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)
|
||||
@GuardedBy("this")
|
||||
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() {
|
||||
return selectedEventIDs;
|
||||
@ -241,14 +232,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 +243,26 @@ public class TimeLineController {
|
||||
synchronized public ReadOnlyBooleanProperty 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);
|
||||
|
||||
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 +299,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);
|
||||
@ -370,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);
|
||||
@ -409,7 +395,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 +411,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;
|
||||
}
|
||||
@ -440,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.",
|
||||
@ -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."})
|
||||
private ArrayList<String> getRebuildReasons() {
|
||||
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 {
|
||||
//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 ingest was running during last rebuild, prompt to rebuild
|
||||
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());
|
||||
@ -474,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
|
||||
@ -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 {
|
||||
|
||||
@Override
|
||||
@ -773,12 +775,10 @@ 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;
|
||||
}
|
||||
}
|
||||
@ -804,32 +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:
|
||||
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user