timeline code from the timeline-events-mrg branch and the thirdparty plaso folder
@ -391,7 +391,13 @@ public class Case {
|
||||
* An item in the central repository has had its comment modified. The
|
||||
* old value is null, the new value is string for current comment.
|
||||
*/
|
||||
CR_COMMENT_CHANGED;
|
||||
CR_COMMENT_CHANGED,
|
||||
/**
|
||||
* An timeline event, such mac time or web activity was added to the
|
||||
* current case. The old value is null and the new value is the
|
||||
* TimelineEvent that was added.
|
||||
*/
|
||||
TIMELINE_EVENT_ADDED;
|
||||
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,7 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException {
|
||||
protected static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException {
|
||||
BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
if (pathIDAttribute != null) {
|
||||
long contentID = pathIDAttribute.getValueLong();
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties
Normal file → Executable file
@ -2,6 +2,8 @@ CTL_MakeTimeline=Timeline
|
||||
CTL_TimeLineTopComponentAction=TimeLineTopComponent
|
||||
CTL_TimeLineTopComponent=Timeline
|
||||
|
||||
FilteredEventsModel.timeRangeProperty.errorMessage=Error getting spanning interval.
|
||||
FilteredEventsModel.timeRangeProperty.errorTitle=Timeline
|
||||
OpenTimelineAction.displayName=Timeline
|
||||
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
||||
OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.
|
||||
@ -9,7 +11,7 @@ PrompDialogManager.buttonType.continueNoUpdate=Continue Without Updating
|
||||
PrompDialogManager.buttonType.showTimeline=Continue
|
||||
PrompDialogManager.buttonType.update=Update DB
|
||||
PromptDialogManager.confirmDuringIngest.contentText=Do you want to continue?
|
||||
PromptDialogManager.confirmDuringIngest.headerText=You are trying to update the Timeline DB before ingest has been completed. The Timeline DB may be incomplete.
|
||||
PromptDialogManager.confirmDuringIngest.headerText=Ingest is still going, and the Timeline may be incomplete.
|
||||
PromptDialogManager.progressDialog.title=Populating Timeline Data
|
||||
PromptDialogManager.rebuildPrompt.details=Details
|
||||
PromptDialogManager.rebuildPrompt.headerText=The Timeline DB is incomplete and/or out of date. Some events may be missing or inaccurate and some features may be unavailable.
|
||||
@ -32,16 +34,6 @@ Timeline.goToButton.text=Go To:
|
||||
Timeline.yearBarChart.x.years=Years
|
||||
Timeline.resultPanel.loading=Loading...
|
||||
|
||||
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.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.ingestWasRunning=The Timeline events database was previously populated while ingest was running: Some events may be missing, incomplete, or inaccurate.
|
||||
TimeLineController.rebuildReasons.outOfDate=The event data is out of date: Not all events will be visible.
|
||||
TimeLineController.rebuildReasons.outOfDateError=Could not determine if the timeline data is out of date.
|
||||
TimeLineController.setEventsDBStale.errMsgNotStale=Failed to mark the timeline db as not stale. Some results may be out of date or missing.
|
||||
TimeLineController.setEventsDBStale.errMsgStale=Failed to mark the timeline db as stale. Some results may be out of date or missing.
|
||||
TimeLinecontroller.setIngestRunning.errMsgNotRunning=Failed to mark the timeline db as populated while ingest was not running. Some results may be out of date or missing.
|
||||
TimeLineController.setIngestRunning.errMsgRunning=Failed to mark the timeline db as populated while ingest was running. Some results may be out of date or missing.
|
||||
TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?
|
||||
TimelineFrame.title=Timeline
|
||||
TimelinePanel.jButton1.text=6m
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties
Normal file → Executable file
0
Core/src/org/sleuthkit/autopsy/timeline/CancellationProgressTask.java
Normal file → Executable file
0
Core/src/org/sleuthkit/autopsy/timeline/ChronoFieldListCell.java
Normal file → Executable file
0
Core/src/org/sleuthkit/autopsy/timeline/FXMLConstructor.java
Normal file → Executable file
702
Core/src/org/sleuthkit/autopsy/timeline/FilteredEventsModel.java
Executable file
@ -0,0 +1,702 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2019 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 com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
import static org.apache.commons.collections4.CollectionUtils.emptyIfNull;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.TagsFilterState;
|
||||
import org.sleuthkit.autopsy.timeline.utils.CacheLoaderImpl;
|
||||
import org.sleuthkit.autopsy.timeline.utils.FilterUtils;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomState;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.DescriptionLoD;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TimelineManager;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskDataException;
|
||||
import org.sleuthkit.datamodel.timeline.EventType;
|
||||
import org.sleuthkit.datamodel.timeline.EventTypeZoomLevel;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineEvent;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.DataSourceFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.DataSourcesFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.EventTypeFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.FileTypesFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.HashHitsFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.HashSetFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.HideKnownFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.RootFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.TagNameFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.TagsFilter;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineFilter.TextFilter;
|
||||
|
||||
/**
|
||||
* This class acts as the model for a TimelineView
|
||||
*
|
||||
* Views can register listeners on properties returned by methods.
|
||||
*
|
||||
* This class is implemented as a filtered view into an underlying
|
||||
* TimelineManager.
|
||||
*
|
||||
* Maintainers, NOTE: as many methods as possible should cache their results so
|
||||
* as to avoid unnecessary db calls through the TimelineManager -jm
|
||||
*
|
||||
* Concurrency Policy: TimelineManager is internally synchronized, so methods
|
||||
* that only access the TimelineManager atomically do not need further
|
||||
* synchronization. All other member state variables should only be accessed
|
||||
* with intrinsic lock of containing FilteredEventsModel held.
|
||||
*
|
||||
*/
|
||||
public final class FilteredEventsModel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FilteredEventsModel.class.getName());
|
||||
|
||||
private final TimelineManager eventManager;
|
||||
|
||||
private final Case autoCase;
|
||||
private final EventBus eventbus = new EventBus("FilteredEventsModel_EventBus"); //NON-NLS
|
||||
|
||||
//Filter and zoome state
|
||||
private final ReadOnlyObjectWrapper<RootFilterState> requestedFilter = new ReadOnlyObjectWrapper<>();
|
||||
private final ReadOnlyObjectWrapper<Interval> requestedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
private final ReadOnlyObjectWrapper<ZoomState> requestedZoomState = new ReadOnlyObjectWrapper<>();
|
||||
private final ReadOnlyObjectWrapper< EventTypeZoomLevel> requestedTypeZoom = new ReadOnlyObjectWrapper<>(EventTypeZoomLevel.BASE_TYPE);
|
||||
private final ReadOnlyObjectWrapper< DescriptionLoD> requestedLOD = new ReadOnlyObjectWrapper<>(DescriptionLoD.SHORT);
|
||||
// end Filter and zoome state
|
||||
|
||||
//caches
|
||||
private final LoadingCache<Object, Long> maxCache;
|
||||
private final LoadingCache<Object, Long> minCache;
|
||||
private final LoadingCache<Long, TimelineEvent> idToEventCache;
|
||||
private final LoadingCache<ZoomState, Map<EventType, Long>> eventCountsCache;
|
||||
/** Map from datasource id to datasource name. */
|
||||
private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap();
|
||||
private final ObservableSet< String> hashSets = FXCollections.observableSet();
|
||||
private final ObservableList<TagName> tagNames = FXCollections.observableArrayList();
|
||||
// end caches
|
||||
|
||||
/**
|
||||
* Make a DataSourceFilter from an entry from the datasourcesMap.
|
||||
*
|
||||
* @param dataSourceEntry A map entry from datasource id to datasource name.
|
||||
*
|
||||
* @return A new DataSourceFilter for the given datsourcesMap entry.
|
||||
*/
|
||||
private static DataSourceFilter newDataSourceFromMapEntry(Map.Entry<Long, String> dataSourceEntry) {
|
||||
return new DataSourceFilter(dataSourceEntry.getValue(), dataSourceEntry.getKey());
|
||||
}
|
||||
|
||||
public FilteredEventsModel(Case autoCase, ReadOnlyObjectProperty<ZoomState> currentStateProperty) throws TskCoreException {
|
||||
this.autoCase = autoCase;
|
||||
this.eventManager = autoCase.getSleuthkitCase().getTimelineManager();
|
||||
populateFilterData();
|
||||
|
||||
//caches
|
||||
idToEventCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(5000L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build(new CacheLoaderImpl<>(eventManager::getEventById));
|
||||
eventCountsCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(1000L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build(new CacheLoaderImpl<>(this::countEventsByType));
|
||||
|
||||
maxCache = CacheBuilder.newBuilder()
|
||||
.build(new CacheLoaderImpl<>(ignored -> eventManager.getMaxTime()));
|
||||
minCache = CacheBuilder.newBuilder()
|
||||
.build(new CacheLoaderImpl<>(ignored -> eventManager.getMinTime()));
|
||||
|
||||
InvalidationListener filterSyncListener = observable -> {
|
||||
RootFilterState rootFilter = filterProperty().get();
|
||||
syncFilters(rootFilter);
|
||||
requestedFilter.set(rootFilter.copyOf());
|
||||
};
|
||||
|
||||
datasourcesMap.addListener(filterSyncListener);
|
||||
hashSets.addListener(filterSyncListener);
|
||||
tagNames.addListener(filterSyncListener);
|
||||
|
||||
requestedFilter.set(getDefaultFilter());
|
||||
|
||||
requestedZoomState.addListener(observable -> {
|
||||
final ZoomState zoomState = requestedZoomState.get();
|
||||
|
||||
if (zoomState != null) {
|
||||
synchronized (FilteredEventsModel.this) {
|
||||
requestedTypeZoom.set(zoomState.getTypeZoomLevel());
|
||||
requestedFilter.set(zoomState.getFilterState());
|
||||
requestedTimeRange.set(zoomState.getTimeRange());
|
||||
requestedLOD.set(zoomState.getDescriptionLOD());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
requestedZoomState.bind(currentStateProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the count of all events that fit the given zoom params organized by
|
||||
* the EvenType of the level specified in the zoomState
|
||||
*
|
||||
* @param zoomState The params that control what events to count and how to
|
||||
* organize the returned map
|
||||
*
|
||||
* @return a map from event type( of the requested level) to event counts
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
private Map<EventType, Long> countEventsByType(ZoomState zoomState) throws TskCoreException {
|
||||
if (zoomState.getTimeRange() == null) {
|
||||
return Collections.emptyMap();
|
||||
} else {
|
||||
return eventManager.countEventsByType(zoomState.getTimeRange().getStartMillis() / 1000,
|
||||
zoomState.getTimeRange().getEndMillis() / 1000,
|
||||
zoomState.getFilterState().getActiveFilter(), zoomState.getTypeZoomLevel());
|
||||
}
|
||||
}
|
||||
|
||||
public TimelineManager getEventManager() {
|
||||
return eventManager;
|
||||
}
|
||||
|
||||
public SleuthkitCase getSleuthkitCase() {
|
||||
return autoCase.getSleuthkitCase();
|
||||
}
|
||||
|
||||
public Interval getBoundingEventsInterval(Interval timeRange, RootFilter filter, DateTimeZone timeZone) throws TskCoreException {
|
||||
return eventManager.getSpanningInterval(timeRange, filter, timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Readonly observable property for the current ZoomState
|
||||
*
|
||||
* @return A readonly observable property for the current ZoomState.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<ZoomState> zoomStateProperty() {
|
||||
return requestedZoomState.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current ZoomState
|
||||
*
|
||||
* @return The current ZoomState
|
||||
*/
|
||||
synchronized public ZoomState getZoomState() {
|
||||
return requestedZoomState.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the data used to determine the available filters.
|
||||
*/
|
||||
synchronized private void populateFilterData() throws TskCoreException {
|
||||
SleuthkitCase skCase = autoCase.getSleuthkitCase();
|
||||
hashSets.addAll(eventManager.getHashSetNames());
|
||||
|
||||
//because there is no way to remove a datasource we only add to this map.
|
||||
for (DataSource ds : eventManager.getSleuthkitCase().getDataSources()) {
|
||||
datasourcesMap.putIfAbsent(ds.getId(), ds.getName());
|
||||
}
|
||||
|
||||
//should this only be tags applied to files or event bearing artifacts?
|
||||
tagNames.setAll(skCase.getTagNamesInUse());
|
||||
}
|
||||
|
||||
/**
|
||||
* "sync" the given root filter with the state of the casee: Disable filters
|
||||
* for tags that are not in use in the case, and add new filters for tags,
|
||||
* hashsets, and datasources, that don't have them. New filters are selected
|
||||
* by default.
|
||||
*
|
||||
* @param rootFilterState the filter state to modify so it is consistent
|
||||
* with the tags in use in the case
|
||||
*/
|
||||
public void syncFilters(RootFilterState rootFilterState) {
|
||||
TagsFilterState tagsFilterState = rootFilterState.getTagsFilterState();
|
||||
for (TagName tagName : tagNames) {
|
||||
tagsFilterState.getFilter().addSubFilter(new TagNameFilter(tagName));
|
||||
}
|
||||
for (FilterState<? extends TagNameFilter> tagFilterState : rootFilterState.getTagsFilterState().getSubFilterStates()) {
|
||||
// disable states for tag names that don't exist in case.
|
||||
tagFilterState.setDisabled(tagNames.contains(tagFilterState.getFilter().getTagName()) == false);
|
||||
}
|
||||
|
||||
DataSourcesFilter dataSourcesFilter = rootFilterState.getDataSourcesFilterState().getFilter();
|
||||
datasourcesMap.entrySet().forEach(entry -> dataSourcesFilter.addSubFilter(newDataSourceFromMapEntry(entry)));
|
||||
|
||||
HashHitsFilter hashSetsFilter = rootFilterState.getHashHitsFilterState().getFilter();
|
||||
for (String hashSet : hashSets) {
|
||||
hashSetsFilter.addSubFilter(new HashSetFilter(hashSet));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a read only view of the time range currently in view.
|
||||
*
|
||||
* @return A read only view of the time range currently in view.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"FilteredEventsModel.timeRangeProperty.errorTitle=Timeline",
|
||||
"FilteredEventsModel.timeRangeProperty.errorMessage=Error getting spanning interval."})
|
||||
synchronized public ReadOnlyObjectProperty<Interval> timeRangeProperty() {
|
||||
if (requestedTimeRange.get() == null) {
|
||||
try {
|
||||
requestedTimeRange.set(getSpanningInterval());
|
||||
} catch (TskCoreException timelineCacheException) {
|
||||
MessageNotifyUtil.Notify.error(Bundle.FilteredEventsModel_timeRangeProperty_errorTitle(),
|
||||
Bundle.FilteredEventsModel_timeRangeProperty_errorMessage());
|
||||
logger.log(Level.SEVERE, "Error getting spanning interval.", timelineCacheException);
|
||||
}
|
||||
}
|
||||
return requestedTimeRange.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<DescriptionLoD> descriptionLODProperty() {
|
||||
return requestedLOD.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<RootFilterState> filterProperty() {
|
||||
return requestedFilter.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<EventTypeZoomLevel> eventTypeZoomProperty() {
|
||||
return requestedTypeZoom.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* The time range currently in view.
|
||||
*
|
||||
* @return The time range currently in view.
|
||||
*/
|
||||
synchronized public Interval getTimeRange() {
|
||||
return getZoomState().getTimeRange();
|
||||
}
|
||||
|
||||
synchronized public DescriptionLoD getDescriptionLOD() {
|
||||
return getZoomState().getDescriptionLOD();
|
||||
}
|
||||
|
||||
synchronized public RootFilterState getFilterState() {
|
||||
return getZoomState().getFilterState();
|
||||
}
|
||||
|
||||
synchronized public EventTypeZoomLevel getEventTypeZoom() {
|
||||
return getZoomState().getTypeZoomLevel();
|
||||
}
|
||||
|
||||
/** Get the default filter used at startup.
|
||||
*
|
||||
* @return the default filter used at startup
|
||||
*/
|
||||
public synchronized RootFilterState getDefaultFilter() {
|
||||
DataSourcesFilter dataSourcesFilter = new DataSourcesFilter();
|
||||
datasourcesMap.entrySet().forEach(dataSourceEntry
|
||||
-> dataSourcesFilter.addSubFilter(newDataSourceFromMapEntry(dataSourceEntry)));
|
||||
|
||||
HashHitsFilter hashHitsFilter = new HashHitsFilter();
|
||||
hashSets.stream().map(HashSetFilter::new).forEach(hashHitsFilter::addSubFilter);
|
||||
|
||||
TagsFilter tagsFilter = new TagsFilter();
|
||||
tagNames.stream().map(TagNameFilter::new).forEach(tagsFilter::addSubFilter);
|
||||
|
||||
FileTypesFilter fileTypesFilter = FilterUtils.createDefaultFileTypesFilter();
|
||||
|
||||
return new RootFilterState(new RootFilter(new HideKnownFilter(),
|
||||
tagsFilter,
|
||||
hashHitsFilter,
|
||||
new TextFilter(),
|
||||
new EventTypeFilter(EventType.ROOT_EVENT_TYPE),
|
||||
dataSourcesFilter,
|
||||
fileTypesFilter,
|
||||
Collections.emptySet()));
|
||||
}
|
||||
|
||||
public Interval getBoundingEventsInterval(DateTimeZone timeZone) throws TskCoreException {
|
||||
return eventManager.getSpanningInterval(zoomStateProperty().get().getTimeRange(), getFilterState().getActiveFilter(), timeZone);
|
||||
}
|
||||
|
||||
public TimelineEvent getEventById(Long eventID) throws TskCoreException {
|
||||
try {
|
||||
return idToEventCache.get(eventID);
|
||||
} catch (ExecutionException ex) {
|
||||
throw new TskCoreException("Error getting cached event from ID", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<TimelineEvent> getEventsById(Collection<Long> eventIDs) throws TskCoreException {
|
||||
Set<TimelineEvent> events = new HashSet<>();
|
||||
for (Long id : eventIDs) {
|
||||
events.add(getEventById(id));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a count of tagnames applied to the given event ids as a map from
|
||||
* tagname displayname to count of tag applications
|
||||
*
|
||||
* @param eventIDsWithTags the event ids to get the tag counts map for
|
||||
*
|
||||
* @return a map from tagname displayname to count of applications
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) throws TskCoreException {
|
||||
return eventManager.getTagCountsByTagName(eventIDsWithTags);
|
||||
}
|
||||
|
||||
public List<Long> getEventIDs(Interval timeRange, FilterState<? extends TimelineFilter> filter) throws TskCoreException {
|
||||
|
||||
final Interval overlap;
|
||||
RootFilter intersection;
|
||||
synchronized (this) {
|
||||
overlap = getSpanningInterval().overlap(timeRange);
|
||||
intersection = getFilterState().intersect(filter).getActiveFilter();
|
||||
}
|
||||
|
||||
return eventManager.getEventIDs(overlap, intersection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of events that pass the requested filter and are within
|
||||
* the given time range.
|
||||
*
|
||||
* NOTE: this method does not change the requested time range
|
||||
*
|
||||
* @param timeRange
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Map<EventType, Long> getEventCounts(Interval timeRange) throws TskCoreException {
|
||||
|
||||
final RootFilterState filter;
|
||||
final EventTypeZoomLevel typeZoom;
|
||||
synchronized (this) {
|
||||
filter = getFilterState();
|
||||
typeZoom = getEventTypeZoom();
|
||||
}
|
||||
try {
|
||||
return eventCountsCache.get(new ZoomState(timeRange, typeZoom, filter, null));
|
||||
} catch (ExecutionException executionException) {
|
||||
throw new TskCoreException("Error getting cached event counts.`1", executionException);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The smallest interval spanning all the events from the case,
|
||||
* ignoring any filters or requested ranges.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Interval getSpanningInterval() throws TskCoreException {
|
||||
return new Interval(getMinTime() * 1000, 1000 + getMaxTime() * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the smallest interval spanning all the given events.
|
||||
*
|
||||
* @param eventIDs The IDs of the events to get a spanning interval arround.
|
||||
*
|
||||
* @return the smallest interval spanning all the given events
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Interval getSpanningInterval(Collection<Long> eventIDs) throws TskCoreException {
|
||||
return eventManager.getSpanningInterval(eventIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time (in seconds from unix epoch) of the absolutely first
|
||||
* event available from the repository, ignoring any filters or
|
||||
* requested ranges
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Long getMinTime() throws TskCoreException {
|
||||
try {
|
||||
return minCache.get("min"); // NON-NLS
|
||||
} catch (ExecutionException ex) {
|
||||
throw new TskCoreException("Error getting cached min time.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time (in seconds from unix epoch) of the absolutely last
|
||||
* event available from the repository, ignoring any filters or
|
||||
* requested ranges
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Long getMaxTime() throws TskCoreException {
|
||||
try {
|
||||
return maxCache.get("max"); // NON-NLS
|
||||
} catch (ExecutionException ex) {
|
||||
throw new TskCoreException("Error getting cached max time.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) throws TskCoreException {
|
||||
ContentTag contentTag = evt.getAddedTag();
|
||||
Content content = contentTag.getContent();
|
||||
Set<Long> updatedEventIDs = addTag(content.getId(), null, contentTag);
|
||||
return postTagsAdded(updatedEventIDs);
|
||||
}
|
||||
|
||||
synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) throws TskCoreException {
|
||||
BlackboardArtifactTag artifactTag = evt.getAddedTag();
|
||||
BlackboardArtifact artifact = artifactTag.getArtifact();
|
||||
Set<Long> updatedEventIDs = addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag);
|
||||
return postTagsAdded(updatedEventIDs);
|
||||
}
|
||||
|
||||
synchronized public boolean handleContentTagDeleted(ContentTagDeletedEvent evt) throws TskCoreException {
|
||||
DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
|
||||
Content content = autoCase.getSleuthkitCase().getContentById(deletedTagInfo.getContentID());
|
||||
boolean tagged = autoCase.getServices().getTagsManager().getContentTagsByContent(content).isEmpty() == false;
|
||||
Set<Long> updatedEventIDs = deleteTag(content.getId(), null, deletedTagInfo.getTagID(), tagged);
|
||||
return postTagsDeleted(updatedEventIDs);
|
||||
}
|
||||
|
||||
synchronized public boolean handleArtifactTagDeleted(BlackBoardArtifactTagDeletedEvent evt) throws TskCoreException {
|
||||
DeletedBlackboardArtifactTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
|
||||
BlackboardArtifact artifact = autoCase.getSleuthkitCase().getBlackboardArtifact(deletedTagInfo.getArtifactID());
|
||||
boolean tagged = autoCase.getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact).isEmpty() == false;
|
||||
Set<Long> updatedEventIDs = deleteTag(artifact.getObjectID(), artifact.getArtifactID(), deletedTagInfo.getTagID(), tagged);
|
||||
return postTagsDeleted(updatedEventIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Set of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @param file The AbstractFile to get derived event IDs
|
||||
* for.
|
||||
* @param includeDerivedArtifacts If true, also get event IDs for events
|
||||
* derived from artifacts derived form this
|
||||
* file. If false, only gets events derived
|
||||
* directly from this file (file system
|
||||
* timestamps).
|
||||
*
|
||||
* @return A Set of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public Set<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) throws TskCoreException {
|
||||
return eventManager.getEventIDsForFile(file, includeDerivedArtifacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to get derived event IDs for.
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given artifact.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) throws TskCoreException {
|
||||
return eventManager.getEventIDsForArtifact(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a TagsAddedEvent to all registered subscribers, if the given set of
|
||||
* updated event IDs is not empty.
|
||||
*
|
||||
* @param updatedEventIDs The set of event ids to be included in the
|
||||
* TagsAddedEvent.
|
||||
*
|
||||
* @return True if an event was posted.
|
||||
*/
|
||||
private boolean postTagsAdded(Set<Long> updatedEventIDs) {
|
||||
boolean tagsUpdated = !updatedEventIDs.isEmpty();
|
||||
if (tagsUpdated) {
|
||||
eventbus.post(new TagsAddedEvent(updatedEventIDs));
|
||||
}
|
||||
return tagsUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a TagsDeletedEvent to all registered subscribers, if the given set
|
||||
* of updated event IDs is not empty.
|
||||
*
|
||||
* @param updatedEventIDs The set of event ids to be included in the
|
||||
* TagsDeletedEvent.
|
||||
*
|
||||
* @return True if an event was posted.
|
||||
*/
|
||||
private boolean postTagsDeleted(Set<Long> updatedEventIDs) {
|
||||
boolean tagsUpdated = !updatedEventIDs.isEmpty();
|
||||
if (tagsUpdated) {
|
||||
eventbus.post(new TagsDeletedEvent(updatedEventIDs));
|
||||
}
|
||||
return tagsUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given object to receive events.
|
||||
*
|
||||
* @param subscriber The object to register. Must implement public methods
|
||||
* annotated with Subscribe.
|
||||
*/
|
||||
synchronized public void registerForEvents(Object subscriber) {
|
||||
eventbus.register(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register the given object, so it no longer receives events.
|
||||
*
|
||||
* @param subscriber The object to un-register.
|
||||
*/
|
||||
synchronized public void unRegisterForEvents(Object subscriber) {
|
||||
eventbus.unregister(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a RefreshRequestedEvent to all registered subscribers.
|
||||
*/
|
||||
public void postRefreshRequest() {
|
||||
eventbus.post(new RefreshRequestedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Post an AutopsyEvent received from another event distribution system
|
||||
* locally to all registered subscribers.
|
||||
*
|
||||
* @param event The event to re-post.
|
||||
*/
|
||||
public void postAutopsyEventLocally(AutopsyEvent event) {
|
||||
eventbus.post(event);
|
||||
}
|
||||
|
||||
public ImmutableList<EventType> getEventTypes() {
|
||||
return eventManager.getEventTypes();
|
||||
}
|
||||
|
||||
synchronized public Set<Long> addTag(long objID, Long artifactID, Tag tag) throws TskCoreException {
|
||||
Set<Long> updatedEventIDs = eventManager.setEventsTagged(objID, artifactID, true);
|
||||
if (isNotEmpty(updatedEventIDs)) {
|
||||
invalidateCaches(updatedEventIDs);
|
||||
}
|
||||
return updatedEventIDs;
|
||||
}
|
||||
|
||||
synchronized public Set<Long> deleteTag(long objID, Long artifactID, long tagID, boolean tagged) throws TskCoreException {
|
||||
Set<Long> updatedEventIDs = eventManager.setEventsTagged(objID, artifactID, tagged);
|
||||
if (isNotEmpty(updatedEventIDs)) {
|
||||
invalidateCaches(updatedEventIDs);
|
||||
}
|
||||
return updatedEventIDs;
|
||||
}
|
||||
|
||||
synchronized public Set<Long> setHashHit(Collection<BlackboardArtifact> artifacts, boolean hasHashHit) throws TskCoreException {
|
||||
Set<Long> updatedEventIDs = new HashSet<>();
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
updatedEventIDs.addAll(eventManager.setEventsHashed(artifact.getObjectID(), hasHashHit));
|
||||
}
|
||||
if (isNotEmpty(updatedEventIDs)) {
|
||||
invalidateCaches(updatedEventIDs);
|
||||
}
|
||||
return updatedEventIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the timeline caches for the given event IDs. Also forces the
|
||||
* filter values to be updated with any new values from the case data.( data
|
||||
* sources, tags, etc)
|
||||
*
|
||||
* @param updatedEventIDs A collection of the event IDs whose cached event
|
||||
* objects should be invalidated. Can be null or an
|
||||
* empty sett to invalidate the general caches, such
|
||||
* as min/max time, or the counts per event type.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public synchronized void invalidateCaches(Collection<Long> updatedEventIDs) throws TskCoreException {
|
||||
minCache.invalidateAll();
|
||||
maxCache.invalidateAll();
|
||||
idToEventCache.invalidateAll(emptyIfNull(updatedEventIDs));
|
||||
eventCountsCache.invalidateAll();
|
||||
|
||||
populateFilterData();
|
||||
|
||||
eventbus.post(new CacheInvalidatedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a cache has been invalidated. The UI should make it
|
||||
* clear that the view is potentially out of date and present an action to
|
||||
* refresh the view.
|
||||
*/
|
||||
public static class CacheInvalidatedEvent {
|
||||
|
||||
private CacheInvalidatedEvent() {
|
||||
}
|
||||
}
|
||||
}
|
0
Core/src/org/sleuthkit/autopsy/timeline/ModifiableProxyLookup.java
Normal file → Executable file
22
Core/src/org/sleuthkit/autopsy/timeline/TimeLineException.java → Core/src/org/sleuthkit/autopsy/timeline/OnStart.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,15 +19,19 @@
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
/**
|
||||
*
|
||||
* The org.openide.modules.OnStart annotation tells NetBeans to invoke this
|
||||
* class's run method.
|
||||
*/
|
||||
public class TimeLineException extends Exception {
|
||||
@org.openide.modules.OnStart
|
||||
public class OnStart implements Runnable {
|
||||
|
||||
public TimeLineException(String string, Exception e) {
|
||||
super(string, e);
|
||||
}
|
||||
|
||||
public TimeLineException(String string) {
|
||||
super(string);
|
||||
/**
|
||||
* This method is invoked by virtue of the OnStart annotation on the this
|
||||
* class
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
TimeLineModule.onStart();
|
||||
}
|
||||
}
|
||||
|
55
Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java
Normal file → Executable file
@ -19,7 +19,6 @@
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javax.swing.ImageIcon;
|
||||
@ -47,10 +46,13 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* An Action that opens the Timeline window. Has methods to open the window in
|
||||
* various specific states (e.g., showing a specific artifact in the List View)
|
||||
*/
|
||||
|
||||
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
|
||||
@ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false)
|
||||
@ActionReferences(value = {
|
||||
@ActionReference(path = "Menu/Tools", position = 102),
|
||||
@ActionReference(path = "Menu/Tools", position = 102)
|
||||
,
|
||||
@ActionReference(path = "Toolbars/Case", position = 102)})
|
||||
public final class OpenTimelineAction extends CallableSystemAction {
|
||||
|
||||
@ -58,19 +60,10 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
private static final Logger logger = Logger.getLogger(OpenTimelineAction.class.getName());
|
||||
private static final int FILE_LIMIT = 6_000_000;
|
||||
|
||||
private static TimeLineController timeLineController = null;
|
||||
|
||||
private final JMenuItem menuItem;
|
||||
private final JButton toolbarButton = new JButton(getName(),
|
||||
new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS
|
||||
|
||||
/**
|
||||
* Invalidate the reference to the controller so that a new one will be
|
||||
* instantiated the next time this action is invoked
|
||||
*/
|
||||
synchronized static void invalidateController() {
|
||||
timeLineController = null;
|
||||
}
|
||||
|
||||
public OpenTimelineAction() {
|
||||
toolbarButton.addActionListener(actionEvent -> performAction());
|
||||
@ -93,24 +86,24 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
public void performAction() {
|
||||
if (tooManyFiles()) {
|
||||
Platform.runLater(PromptDialogManager::showTooManyFiles);
|
||||
synchronized (OpenTimelineAction.this) {
|
||||
if (timeLineController != null) {
|
||||
timeLineController.shutDownTimeLine();
|
||||
}
|
||||
}
|
||||
setEnabled(false);
|
||||
}else if("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) {
|
||||
} else if ("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) {
|
||||
Platform.runLater(PromptDialogManager::showTimeLineDisabledMessage);
|
||||
setEnabled(false);
|
||||
}else {
|
||||
showTimeline();
|
||||
} else {
|
||||
try {
|
||||
showTimeline();
|
||||
} catch (TskCoreException ex) {
|
||||
MessageNotifyUtil.Message.error(Bundle.OpenTimelineAction_settingsErrorMessage());
|
||||
logger.log(Level.SEVERE, "Error showingtimeline.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.",
|
||||
"OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."})
|
||||
synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) {
|
||||
synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException {
|
||||
try {
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
if (currentCase.hasData() == false) {
|
||||
@ -118,20 +111,8 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
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.showTimeLine(file, artifact);
|
||||
|
||||
} catch (IOException iOException) {
|
||||
MessageNotifyUtil.Message.error(Bundle.OpenTimelineAction_settingsErrorMessage());
|
||||
logger.log(Level.SEVERE, "Failed to initialize per case timeline settings.", iOException);
|
||||
}
|
||||
TimeLineController controller = TimeLineModule.getController();
|
||||
controller.showTimeLine(file, artifact);
|
||||
} catch (NoCurrentCaseException e) {
|
||||
//there is no case... Do nothing.
|
||||
}
|
||||
@ -141,7 +122,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
* Open the Timeline window with the default initial view.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showTimeline() {
|
||||
public void showTimeline() throws TskCoreException {
|
||||
showTimeline(null, null);
|
||||
}
|
||||
|
||||
@ -153,7 +134,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
* @param file The AbstractFile to show in the Timeline.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showFileInTimeline(AbstractFile file) {
|
||||
public void showFileInTimeline(AbstractFile file) throws TskCoreException {
|
||||
showTimeline(file, null);
|
||||
}
|
||||
|
||||
@ -164,7 +145,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
* @param artifact The BlackboardArtifact to show in the Timeline.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showArtifactInTimeline(BlackboardArtifact artifact) {
|
||||
public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException {
|
||||
showTimeline(null, artifact);
|
||||
}
|
||||
|
||||
|
@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2016-2018 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 Path propertiesPath;
|
||||
|
||||
PerCaseTimelineProperties(Case autopsyCase) {
|
||||
Objects.requireNonNull(autopsyCase, "Case must not be null");
|
||||
propertiesPath = Paths.get(autopsyCase.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;
|
||||
}
|
||||
}
|
||||
}
|
3
Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java
Normal file → Executable file
@ -152,7 +152,7 @@ public final class PromptDialogManager {
|
||||
* @return True if they want to continue anyways.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"PromptDialogManager.confirmDuringIngest.headerText=You are trying to update the Timeline DB before ingest has been completed. The Timeline DB may be incomplete.",
|
||||
"PromptDialogManager.confirmDuringIngest.headerText=Ingest is still going, and the Timeline may be incomplete.",
|
||||
"PromptDialogManager.confirmDuringIngest.contentText=Do you want to continue?"})
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
boolean confirmDuringIngest() {
|
||||
@ -235,5 +235,4 @@ public final class PromptDialogManager {
|
||||
dialog.setHeaderText(Bundle.PromptDialogManager_showTimeLineDisabledMessage_headerText());
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
}
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.fxml
Normal file → Executable file
46
Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -25,10 +25,12 @@ import java.time.Instant;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
@ -58,14 +60,15 @@ import org.controlsfx.validation.Validator;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.EventTypeUtils;
|
||||
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.timeline.EventType;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineEvent;
|
||||
|
||||
/**
|
||||
* A Dialog that, given an AbstractFile or BlackBoardArtifact, allows the user
|
||||
@ -93,13 +96,13 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
ChronoField.SECOND_OF_MINUTE);
|
||||
|
||||
@FXML
|
||||
private TableView<SingleEvent> eventTable;
|
||||
private TableView<TimelineEvent> eventTable;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SingleEvent, EventType> typeColumn;
|
||||
private TableColumn<TimelineEvent, EventType> typeColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SingleEvent, Long> dateTimeColumn;
|
||||
private TableColumn<TimelineEvent, Long> dateTimeColumn;
|
||||
|
||||
@FXML
|
||||
private Spinner<Integer> amountSpinner;
|
||||
@ -112,8 +115,6 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
|
||||
private final VBox contentRoot = new VBox();
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private final ValidationSupport validationSupport = new ValidationSupport();
|
||||
|
||||
/**
|
||||
@ -124,10 +125,8 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
* from.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"ShowInTimelineDialog.amountValidator.message=The entered amount must only contain digits."
|
||||
})
|
||||
private ShowInTimelineDialog(TimeLineController controller, List<Long> eventIDS) {
|
||||
this.controller = controller;
|
||||
"ShowInTimelineDialog.amountValidator.message=The entered amount must only contain digits."})
|
||||
private ShowInTimelineDialog(TimeLineController controller, Collection<Long> eventIDS) throws TskCoreException {
|
||||
|
||||
//load dialog content fxml
|
||||
final String name = "nbres:/" + StringUtils.replace(ShowInTimelineDialog.class.getPackage().getName(), ".", "/") + "/ShowInTimelineDialog.fxml"; // NON-NLS
|
||||
@ -195,7 +194,16 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
dateTimeColumn.setCellFactory(param -> new DateTimeTableCell<>());
|
||||
|
||||
//add events to table
|
||||
eventTable.getItems().setAll(eventIDS.stream().map(controller.getEventsModel()::getEventById).collect(Collectors.toSet()));
|
||||
Set<TimelineEvent> events = new HashSet<>();
|
||||
FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
for (Long eventID : eventIDS) {
|
||||
try {
|
||||
events.add(eventsModel.getEventById(eventID));
|
||||
} catch (TskCoreException ex) {
|
||||
throw new TskCoreException("Error getting event by id.", ex);
|
||||
}
|
||||
}
|
||||
eventTable.getItems().setAll(events);
|
||||
eventTable.setPrefHeight(Math.min(200, 24 * eventTable.getItems().size() + 28));
|
||||
}
|
||||
|
||||
@ -207,7 +215,7 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
* @param artifact The BlackboardArtifact to configure this dialog for.
|
||||
*/
|
||||
@NbBundle.Messages({"ShowInTimelineDialog.artifactTitle=View Result in Timeline."})
|
||||
ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) {
|
||||
ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) throws TskCoreException {
|
||||
//get events IDs from artifact
|
||||
this(controller, controller.getEventsModel().getEventIDsForArtifact(artifact));
|
||||
|
||||
@ -237,7 +245,7 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
@NbBundle.Messages({"# {0} - file path",
|
||||
"ShowInTimelineDialog.fileTitle=View {0} in timeline.",
|
||||
"ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."})
|
||||
ShowInTimelineDialog(TimeLineController controller, AbstractFile file) {
|
||||
ShowInTimelineDialog(TimeLineController controller, AbstractFile file) throws TskCoreException {
|
||||
this(controller, controller.getEventsModel().getEventIDsForFile(file, false));
|
||||
|
||||
/*
|
||||
@ -293,11 +301,11 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
/**
|
||||
* Construct this Dialog's "result" from the given event.
|
||||
*
|
||||
* @param selectedEvent The SingleEvent to include in the EventInTimeRange
|
||||
* @param selectedEvent The TimeLineEvent to include in the EventInTimeRange
|
||||
*
|
||||
* @return The EventInTimeRange that is the "result" of this dialog.
|
||||
*/
|
||||
private ViewInTimelineRequestedEvent makeEventInTimeRange(SingleEvent selectedEvent) {
|
||||
private ViewInTimelineRequestedEvent makeEventInTimeRange(TimelineEvent selectedEvent) {
|
||||
Duration selectedDuration = unitComboBox.getSelectionModel().getSelectedItem().getBaseUnit().getDuration().multipliedBy(amountSpinner.getValue());
|
||||
Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getStartMillis()), selectedDuration);
|
||||
return new ViewInTimelineRequestedEvent(Collections.singleton(selectedEvent.getEventID()), range);
|
||||
@ -356,7 +364,7 @@ final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(item.getDisplayName());
|
||||
setGraphic(new ImageView(item.getFXImage()));
|
||||
setGraphic(new ImageView(EventTypeUtils.getImagePath(item)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
715
Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java
Normal file → Executable file
131
Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java
Executable file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Manages listeners and the controller.
|
||||
*
|
||||
*/
|
||||
public class TimeLineModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(TimeLineModule.class.getName());
|
||||
|
||||
private static final Object controllerLock = new Object();
|
||||
private static TimeLineController controller;
|
||||
|
||||
/**
|
||||
* provides static utilities, can not be instantiated
|
||||
*/
|
||||
private TimeLineModule() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance of the controller for the current case
|
||||
*
|
||||
* @return the controller for the current case.
|
||||
*
|
||||
* @throws NoCurrentCaseException If there is no case open.
|
||||
* @throws TskCoreException If there was a problem accessing the case
|
||||
* database.
|
||||
*
|
||||
*/
|
||||
public static TimeLineController getController() throws NoCurrentCaseException, TskCoreException {
|
||||
synchronized (controllerLock) {
|
||||
if (controller == null) {
|
||||
controller = new TimeLineController(Case.getCurrentCaseThrows());
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked by virtue of the OnStart annotation on the OnStart
|
||||
* class class
|
||||
*/
|
||||
static void onStart() {
|
||||
Platform.setImplicitExit(false);
|
||||
logger.info("Setting up TimeLine listeners"); //NON-NLS
|
||||
|
||||
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
|
||||
Case.addPropertyChangeListener(new CaseEventListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for case events.
|
||||
*/
|
||||
static private class CaseEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
try {
|
||||
getController().handleCaseEvent(evt);
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
// ignore
|
||||
return;
|
||||
} catch (TskCoreException ex) {
|
||||
MessageNotifyUtil.Message.error("Error creating timeline controller.");
|
||||
logger.log(Level.SEVERE, "Error creating timeline controller", ex);
|
||||
}
|
||||
|
||||
if (Case.Events.valueOf(evt.getPropertyName()).equals(CURRENT_CASE)) {
|
||||
// we care only about case closing here
|
||||
if (evt.getNewValue() == null) {
|
||||
synchronized (controllerLock) {
|
||||
if (controller != null) {
|
||||
SwingUtilities.invokeLater(controller::shutDownTimeLine);
|
||||
}
|
||||
controller = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for IngestModuleEvents
|
||||
*/
|
||||
static private class IngestModuleEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
try {
|
||||
getController().handleIngestModuleEvent(evt);
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
// ignore
|
||||
return;
|
||||
} catch (TskCoreException ex) {
|
||||
MessageNotifyUtil.Message.error("Error creating timeline controller.");
|
||||
logger.log(Level.SEVERE, "Error creating timeline controller", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
0
Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form
Normal file → Executable file
37
Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-2019 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
@ -58,7 +59,6 @@ import org.openide.windows.RetainLocation;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.actions.AddBookmarkTagAction;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
@ -91,7 +91,6 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(TimeLineTopComponent.class.getName());
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
@ -103,7 +102,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private final ExplorerManager explorerManager = new ExplorerManager();
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
/**
|
||||
* Lookup that will be exposed through the (Global Actions Context)
|
||||
@ -165,7 +164,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
*/
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
List<Long> selectedEventIDs = controller.getSelectedEventIDs();
|
||||
// make a copy because this list gets updated as the user navigates around
|
||||
// and causes concurrent access exceptions
|
||||
List<Long> selectedEventIDs = ImmutableList.copyOf(controller.getSelectedEventIDs());
|
||||
|
||||
//depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly.
|
||||
switch (controller.getViewMode()) {
|
||||
@ -196,9 +197,6 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
contentViewerPanel.setNode(null);
|
||||
}
|
||||
});
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
logger.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
@ -254,12 +252,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a "shell" version of the top component for the Timeline
|
||||
* feature which has only Swing components, no controller, and no listeners.
|
||||
* This constructor conforms to the NetBeans window system requirement that
|
||||
* all top components have a public, no argument constructor.
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimeLineController for this topcomponent.
|
||||
*/
|
||||
public TimeLineTopComponent() {
|
||||
public TimeLineTopComponent(TimeLineController controller) {
|
||||
initComponents();
|
||||
associateLookup(proxyLookup);
|
||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||
@ -268,6 +265,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
|
||||
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ExternalViewerShortcutAction.EXTERNAL_VIEWER_SHORTCUT, "useExternalViewer"); //NON-NLS
|
||||
getActionMap().put("useExternalViewer", ExternalViewerShortcutAction.getInstance()); //NON-NLS
|
||||
this.controller = controller;
|
||||
|
||||
//create linked result and content views
|
||||
contentViewerPanel = new DataContentExplorerPanel();
|
||||
@ -279,22 +277,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
|
||||
dataResultPanel.open(); //get the explorermanager
|
||||
contentViewerPanel.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a fully functional top component for the Timeline feature.
|
||||
*
|
||||
* @param controller The TimeLineController for this top component.
|
||||
*/
|
||||
public TimeLineTopComponent(TimeLineController controller) {
|
||||
this();
|
||||
|
||||
this.controller = controller;
|
||||
|
||||
Platform.runLater(this::initFXComponents);
|
||||
|
||||
//set up listeners
|
||||
TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
|
||||
TimeLineController.timeZoneProperty().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
|
||||
controller.getSelectedEventIDs().addListener(selectedEventsListener);
|
||||
|
||||
//Listen to ViewMode and adjust GUI componenets as needed.
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java
Normal file → Executable file
20
Core/src/org/sleuthkit/autopsy/timeline/WrappingListCell.java
Normal file → Executable file
@ -1,9 +1,21 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-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 javafx.scene.control.ListCell;
|
||||
|
319
Core/src/org/sleuthkit/autopsy/timeline/actions/AddManualEvent.java
Executable file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 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.actions;
|
||||
|
||||
import java.awt.Dialog;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import static java.util.Arrays.asList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.DialogPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.util.StringConverter;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.SwingUtilities;
|
||||
import jfxtras.scene.control.LocalDateTimeTextField;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
import org.controlsfx.tools.ValueExtractor;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.datamodel.Blackboard;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TL_EVENT_TYPE;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.timeline.EventType;
|
||||
|
||||
/**
|
||||
* Action that allows the user the manually create timeline events. It prompts
|
||||
* the user for event data and then adds it to the case via an artifact.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"AddManualEvent.text=Add Event",
|
||||
"AddManualEvent.longText=Manually add an event to the timeline."})
|
||||
public class AddManualEvent extends Action {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(AddManualEvent.class.getName());
|
||||
private static final String MANUAL_CREATION = "Manual Creation"; //NON-NLS
|
||||
private static final Image ADD_EVENT_IMAGE = new Image("/org/sleuthkit/autopsy/timeline/images/add.png", 16, 16, true, true, true); // NON-NLS
|
||||
|
||||
/**
|
||||
* Initialize the custom value extractor used by the ValidationSupport for
|
||||
* the LocalDateTimeTextField in the EventCreationDialogPane.
|
||||
*/
|
||||
static {
|
||||
ValueExtractor.addObservableValueExtractor(LocalDateTimeTextField.class::isInstance,
|
||||
control -> ((LocalDateTimeTextField) control).localDateTimeProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Action that allows the user the manually create timeline
|
||||
* events. It prompts the user for event data with a dialog and then adds it
|
||||
* to the case via an artifact. The datetiem in the dialog will be set to
|
||||
* "now" when the action is invoked.
|
||||
*
|
||||
* @param controller The controller for this action to use.
|
||||
*
|
||||
*/
|
||||
public AddManualEvent(TimeLineController controller) {
|
||||
this(controller, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Action that allows the user the manually create timeline
|
||||
* events. It prompts the user for event data with a dialog and then adds it
|
||||
* to the case via an artifact.
|
||||
*
|
||||
* @param controller The controller for this action to use.
|
||||
* @param epochMillis The initial datetime to populate the dialog with. The
|
||||
* user can ove ride this.
|
||||
*/
|
||||
public AddManualEvent(TimeLineController controller, Long epochMillis) {
|
||||
super(Bundle.AddManualEvent_text());
|
||||
setGraphic(new ImageView(ADD_EVENT_IMAGE));
|
||||
setLongText(Bundle.AddManualEvent_longText());
|
||||
|
||||
setEventHandler(actionEvent -> SwingUtilities.invokeLater(() -> {
|
||||
JEventCreationDialog dialog = new JEventCreationDialog(controller, epochMillis, SwingUtilities.windowForComponent(controller.getTopComponent()));
|
||||
dialog.setVisible(true);
|
||||
//actual event creation happens in the ok button listener.
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the supplied ManualEventInfo to make an TSK_TL_EVENT artifact which
|
||||
* will trigger adding a TimelineEvent.
|
||||
*
|
||||
* @param eventInfo The ManualEventInfo with the info needed to create an
|
||||
* event.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"AddManualEvent.createArtifactFailed=Failed to create artifact for event.",
|
||||
"AddManualEvent.postArtifactFailed=Failed to post artifact to blackboard."})
|
||||
private void addEvent(TimeLineController controller, ManualEventInfo eventInfo) throws IllegalArgumentException {
|
||||
SleuthkitCase sleuthkitCase = controller.getEventsModel().getSleuthkitCase();
|
||||
|
||||
try {
|
||||
//Use the current examiners name plus a fixed string as the source / module name.
|
||||
String source = MANUAL_CREATION + ": " + sleuthkitCase.getCurrentExaminer().getLoginName();
|
||||
|
||||
BlackboardArtifact artifact = sleuthkitCase.newBlackboardArtifact(TSK_TL_EVENT, eventInfo.datasource.getId());
|
||||
artifact.addAttributes(asList(
|
||||
new BlackboardAttribute(
|
||||
TSK_TL_EVENT_TYPE, source,
|
||||
EventType.USER_CREATED.getTypeID()),
|
||||
new BlackboardAttribute(
|
||||
TSK_DESCRIPTION, source,
|
||||
eventInfo.description),
|
||||
new BlackboardAttribute(
|
||||
TSK_DATETIME, source,
|
||||
eventInfo.time)
|
||||
));
|
||||
try {
|
||||
sleuthkitCase.getBlackboard().postArtifact(artifact, source);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, "Error posting artifact to the blackboard.", ex); //NON-NLS
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.AddManualEvent_postArtifactFailed(), ButtonType.OK).showAndWait();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error creatig new artifact.", ex); //NON-NLS
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.AddManualEvent_createArtifactFailed(), ButtonType.OK).showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of JDialog used to dislpay the JFXPanel with the event creation
|
||||
* widgets.
|
||||
*/
|
||||
private final class JEventCreationDialog extends JDialog {
|
||||
|
||||
private final JFXPanel jfxPanel = new JFXPanel();
|
||||
|
||||
private JEventCreationDialog(TimeLineController controller, Long epochMillis, java.awt.Window owner) {
|
||||
super(owner, Bundle.AddManualEvent_text(), Dialog.ModalityType.DOCUMENT_MODAL);
|
||||
setIconImages(owner.getIconImages());
|
||||
setResizable(false);
|
||||
add(jfxPanel);
|
||||
|
||||
// make and configure the JavaFX components.
|
||||
Platform.runLater(() -> {
|
||||
// Custom DialogPane defined below.
|
||||
EventCreationDialogPane customPane = new EventCreationDialogPane(controller, epochMillis);
|
||||
//cancel button just closes the dialog
|
||||
((ButtonBase) customPane.lookupButton(ButtonType.CANCEL)).setOnAction(event -> dispose());
|
||||
//configure ok button to pull ManualEventInfo object and add it to case.
|
||||
((ButtonBase) customPane.lookupButton(ButtonType.OK)).setOnAction(event -> {
|
||||
ManualEventInfo manualEventInfo = customPane.getManualEventInfo();
|
||||
if (manualEventInfo != null) {
|
||||
addEvent(controller, manualEventInfo);
|
||||
}
|
||||
dispose(); //close and dispose the dialog.
|
||||
});
|
||||
|
||||
jfxPanel.setScene(new Scene(customPane));
|
||||
customPane.installValidation();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//size and position dialog on EDT
|
||||
pack();
|
||||
setLocationRelativeTo(owner);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The DialogPane that hosts the controls/widgets that allows the user
|
||||
* to enter the event information.
|
||||
*/
|
||||
private class EventCreationDialogPane extends DialogPane {
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<DataSource> dataSourceChooser;
|
||||
@FXML
|
||||
private TextField descriptionTextField;
|
||||
@FXML
|
||||
private ComboBox<String> timeZoneChooser;
|
||||
@FXML
|
||||
private LocalDateTimeTextField timePicker;
|
||||
|
||||
private final List<String> timeZoneList = TimeZoneUtils.createTimeZoneList();
|
||||
private final ValidationSupport validationSupport = new ValidationSupport();
|
||||
private final TimeLineController controller;
|
||||
|
||||
private EventCreationDialogPane(TimeLineController controller, Long epochMillis) {
|
||||
this.controller = controller;
|
||||
FXMLConstructor.construct(this, "EventCreationDialog.fxml"); //NON-NLS
|
||||
if (epochMillis == null) {
|
||||
timePicker.setLocalDateTime(LocalDateTime.now());
|
||||
} else {
|
||||
timePicker.setLocalDateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), TimeLineController.getTimeZoneID()));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"# {0} - datasource name", "# {1} - datasource id",
|
||||
"AddManualEvent.EventCreationDialogPane.dataSourceStringConverter.template={0} (ID: {1})",
|
||||
"AddManualEvent.EventCreationDialogPane.initialize.dataSourcesError=Error getting datasources in case."})
|
||||
private void initialize() {
|
||||
assert descriptionTextField != null : "fx:id=\"descriptionTextField\" was not injected: check your FXML file 'EventCreationDialog.fxml'.";//NON-NLS
|
||||
|
||||
timeZoneChooser.getItems().setAll(timeZoneList);
|
||||
timeZoneChooser.getSelectionModel().select(TimeZoneUtils.createTimeZoneString(TimeLineController.getTimeZone()));
|
||||
TextFields.bindAutoCompletion(timeZoneChooser.getEditor(), timeZoneList);
|
||||
|
||||
dataSourceChooser.setConverter(new StringConverter<DataSource>() {
|
||||
@Override
|
||||
public String toString(DataSource dataSource) {
|
||||
return Bundle.AddManualEvent_EventCreationDialogPane_dataSourceStringConverter_template(dataSource.getName(), dataSource.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource fromString(String string) {
|
||||
throw new UnsupportedOperationException(); // This method should never get called.
|
||||
}
|
||||
});
|
||||
try {
|
||||
dataSourceChooser.getItems().setAll(controller.getAutopsyCase().getSleuthkitCase().getDataSources());
|
||||
dataSourceChooser.getSelectionModel().select(0);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting datasources in case.", ex);//NON-NLS
|
||||
SwingUtilities.invokeLater(() -> MessageNotifyUtil.Message.error(Bundle.AddManualEvent_EventCreationDialogPane_initialize_dataSourcesError()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install/Configure the ValidationSupport.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"AddManualEvent.validation.description=Description is required.",
|
||||
"AddManualEvent.validation.datetime=Invalid datetime",
|
||||
"AddManualEvent.validation.timezone=Invalid time zone",})
|
||||
private void installValidation() {
|
||||
validationSupport.registerValidator(descriptionTextField, false,
|
||||
Validator.createEmptyValidator(Bundle.AddManualEvent_validation_description()));
|
||||
validationSupport.registerValidator(timePicker, false,
|
||||
Validator.createPredicateValidator(Objects::nonNull, Bundle.AddManualEvent_validation_description()));
|
||||
validationSupport.registerValidator(timeZoneChooser, false,
|
||||
Validator.createPredicateValidator((String zone) -> timeZoneList.contains(zone.trim()), Bundle.AddManualEvent_validation_timezone()));
|
||||
|
||||
validationSupport.initInitialDecoration();
|
||||
|
||||
//The ok button is only enabled if all fields are validated.
|
||||
lookupButton(ButtonType.OK).disableProperty().bind(validationSupport.invalidProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the user entered data into a ManulEventInfo object.
|
||||
*
|
||||
* @return The ManualEventInfo containing the user entered event
|
||||
* info.
|
||||
*/
|
||||
private ManualEventInfo getManualEventInfo() {
|
||||
//Trim off the offset part of the string from the chooser, to get something that ZoneId can parse.
|
||||
String zone = StringUtils.substringAfter(timeZoneChooser.getValue(), ")").trim(); //NON-NLS
|
||||
long toEpochSecond = timePicker.getLocalDateTime().atZone(ZoneId.of(zone)).toEpochSecond();
|
||||
return new ManualEventInfo(dataSourceChooser.getValue(), descriptionTextField.getText(), toEpochSecond);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Info required from user to manually create a timeline event.
|
||||
*/
|
||||
private static class ManualEventInfo {
|
||||
|
||||
private final DataSource datasource;
|
||||
private final String description;
|
||||
private final long time;
|
||||
|
||||
private ManualEventInfo(DataSource datasource, String description, long time) {
|
||||
this.datasource = datasource;
|
||||
this.description = description;
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
}
|
2
Core/src/org/sleuthkit/autopsy/timeline/actions/Back.java
Normal file → Executable file
@ -32,7 +32,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
//TODO: This and the corresponding imageanalyzer action are identical except for the type of the controller... abstract something! -jm
|
||||
public class Back extends Action {
|
||||
|
||||
private static final Image BACK_IMAGE = new Image("/org/sleuthkit/autopsy/images/resultset_previous.png", 16, 16, true, true, true); // NON-NLS
|
||||
private static final Image BACK_IMAGE = new Image("/org/sleuthkit/autopsy/timeline/images/arrow-180.png", 16, 16, true, true, true); // NON-NLS
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
|
@ -1,3 +1,14 @@
|
||||
AddManualEvent.createArtifactFailed=Failed to create artifact for event.
|
||||
# {0} - datasource name
|
||||
# {1} - datasource id
|
||||
AddManualEvent.EventCreationDialogPane.dataSourceStringConverter.template={0} (ID: {1})
|
||||
AddManualEvent.EventCreationDialogPane.initialize.dataSourcesError=Error getting datasources in case.
|
||||
AddManualEvent.longText=Manually add an event to the timeline.
|
||||
AddManualEvent.postArtifactFailed=Failed to post artifact to blackboard.
|
||||
AddManualEvent.text=Add Event
|
||||
AddManualEvent.validation.datetime=Invalid datetime
|
||||
AddManualEvent.validation.description=Description is required.
|
||||
AddManualEvent.validation.timezone=Invalid time zone
|
||||
# {0} - action accelerator keys
|
||||
Back.longText=Back: {0}\nGo back to the last view settings.
|
||||
Back.text=Back
|
||||
@ -10,8 +21,6 @@ OpenReportAction.MissingReportFileMessage=The report file no longer exists.
|
||||
OpenReportAction.NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.
|
||||
OpenReportAction.NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.
|
||||
OpenReportAction.ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.
|
||||
RebuildDataBase.longText=Update the DB to include new events.
|
||||
RebuildDataBase.text=Update DB
|
||||
ResetFilters.text=Reset all filters
|
||||
RestFilters.longText=Reset all filters to their default state.
|
||||
SaveSnapShotAsReport.action.dialogs.title=Timeline
|
||||
@ -32,8 +41,12 @@ ViewArtifactInTimelineAction.displayName=View Result in Timeline...
|
||||
ViewFileInTimelineAction.viewFile.displayName=View File in Timeline...
|
||||
ViewFileInTimelineAction.viewSourceFile.displayName=View Source File in Timeline...
|
||||
ZoomIn.action.text=Zoom in
|
||||
ZoomIn.errorMessage=Error zooming in.
|
||||
ZoomIn.longText=Zoom in to view about half as much time.
|
||||
ZoomOut.action.text=Zoom out
|
||||
ZoomOut.disabledProperty.errorMessage=Error getting spanning interval.
|
||||
ZoomOut.errorMessage=Error zooming out.
|
||||
ZoomOut.longText=Zoom out to view about 50% more time.
|
||||
ZoomToEvents.action.text=Zoom to events
|
||||
ZoomToEvents.disabledProperty.errorMessage=Error getting spanning interval.
|
||||
ZoomToEvents.longText=Zoom out to show the nearest events.
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/actions/Bundle_ja.properties
Normal file → Executable file
52
Core/src/org/sleuthkit/autopsy/timeline/actions/EventCreationDialog.fxml
Executable file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.ButtonType?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
<?import javafx.scene.control.ComboBox?>
|
||||
<?import javafx.scene.control.DialogPane?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import jfxtras.scene.control.LocalDateTimeTextField?>
|
||||
|
||||
<fx:root expanded="true" maxHeight="159.0" maxWidth="555.0" minHeight="159.0" minWidth="555.0" prefHeight="159.0" prefWidth="555.0" type="DialogPane" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<buttonTypes>
|
||||
<ButtonType fx:constant="OK" />
|
||||
<ButtonType fx:constant="CANCEL" />
|
||||
</buttonTypes>
|
||||
<content>
|
||||
<GridPane fx:id="gridPane" hgap="5.0" vgap="5.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER" maxWidth="93.0" minWidth="10.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="193.0" minWidth="10.0" />
|
||||
<ColumnConstraints hgrow="NEVER" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints fillHeight="false" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints fillHeight="false" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints fillHeight="false" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="Description:" GridPane.rowIndex="1" />
|
||||
<TextField fx:id="descriptionTextField" prefHeight="26.0" prefWidth="278.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="1" />
|
||||
<Label text="DateTime" GridPane.rowIndex="2" />
|
||||
<Label text="Time Zone" GridPane.columnIndex="2" GridPane.rowIndex="2">
|
||||
<padding>
|
||||
<Insets left="15.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
<ComboBox fx:id="timeZoneChooser" editable="true" prefHeight="28.0" prefWidth="214.0" GridPane.columnIndex="3" GridPane.rowIndex="2" />
|
||||
<LocalDateTimeTextField fx:id="timePicker" prefHeight="26.0" prefWidth="166.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||
<Label text="DataSource:" />
|
||||
<ChoiceBox fx:id="dataSourceChooser" prefHeight="25.0" prefWidth="168.0" GridPane.columnIndex="1" GridPane.columnSpan="3" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding>
|
||||
</GridPane>
|
||||
</content>
|
||||
</fx:root>
|
2
Core/src/org/sleuthkit/autopsy/timeline/actions/Forward.java
Normal file → Executable file
@ -32,7 +32,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
//TODO: This and the corresponding imageanalyzer action are identical except for the type of the controller... abstract something! -jm
|
||||
public class Forward extends Action {
|
||||
|
||||
private static final Image FORWARD_IMAGE = new Image("/org/sleuthkit/autopsy/images/resultset_next.png", 16, 16, true, true, true); // NON-NLS
|
||||
private static final Image FORWARD_IMAGE = new Image("/org/sleuthkit/autopsy/timeline/images/arrow.png", 16, 16, true, true, true); // NON-NLS
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
|
6
Core/src/org/sleuthkit/autopsy/timeline/actions/ResetFilters.java
Normal file → Executable file
@ -22,8 +22,8 @@ import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.event.ActionEvent;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
|
||||
/**
|
||||
* Action that resets the filters to their initial/default state.
|
||||
@ -44,12 +44,12 @@ public class ResetFilters extends Action {
|
||||
eventsModel = controller.getEventsModel();
|
||||
disabledProperty().bind(new BooleanBinding() {
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty());
|
||||
bind(eventsModel.zoomStateProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
return eventsModel.zoomParametersProperty().getValue().getFilter().equals(eventsModel.getDefaultFilter());
|
||||
return eventsModel.zoomStateProperty().getValue().getFilterState().equals(eventsModel.getDefaultFilter());
|
||||
}
|
||||
});
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
|
2
Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java
Normal file → Executable file
@ -141,7 +141,7 @@ public class SaveSnapshotAsReport extends Action {
|
||||
reportMainFilePath = new SnapShotReportWriter(currentCase,
|
||||
reportFolderPath,
|
||||
reportName,
|
||||
controller.getEventsModel().getZoomParamaters(),
|
||||
controller.getEventsModel().getZoomState(),
|
||||
generationDate, snapshot).writeReport();
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error writing report to disk at " + reportFolderPath, ex); //NON_NLS
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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.actions;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
|
||||
/**
|
||||
* An action that rebuilds the timeline database to include any new results from
|
||||
* ingest.
|
||||
*/
|
||||
public class UpdateDB extends Action {
|
||||
|
||||
private static final Image DB_REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/database_refresh.png");
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimeLineController for this action.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"RebuildDataBase.text=Update DB",
|
||||
"RebuildDataBase.longText=Update the DB to include new events."})
|
||||
public UpdateDB(TimeLineController controller) {
|
||||
super(Bundle.RebuildDataBase_text());
|
||||
setLongText(Bundle.RebuildDataBase_longText());
|
||||
setGraphic(new ImageView(DB_REFRESH));
|
||||
setEventHandler(actionEvent -> controller.rebuildRepo());
|
||||
disabledProperty().bind(controller.eventsDBStaleProperty().not());
|
||||
}
|
||||
}
|
37
Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java
Normal file → Executable file
@ -19,14 +19,13 @@
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.actions.SystemAction;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -36,13 +35,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
public final class ViewArtifactInTimelineAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Set<ArtifactEventType> ARTIFACT_EVENT_TYPES =
|
||||
EventType.allTypes.stream()
|
||||
.filter((EventType t) -> t instanceof ArtifactEventType)
|
||||
.map(ArtifactEventType.class::cast)
|
||||
.collect(Collectors.toSet());
|
||||
private static final Logger logger = Logger.getLogger(ViewFileInTimelineAction.class.getName());
|
||||
|
||||
private final BlackboardArtifact artifact;
|
||||
|
||||
@ -54,26 +47,26 @@ public final class ViewArtifactInTimelineAction extends AbstractAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
SystemAction.get(OpenTimelineAction.class).showArtifactInTimeline(artifact);
|
||||
try {
|
||||
SystemAction.get(OpenTimelineAction.class).showArtifactInTimeline(artifact);
|
||||
} catch (TskCoreException ex) {
|
||||
MessageNotifyUtil.Message.error("Error opening Timeline");
|
||||
logger.log(Level.SEVERE, "Error showing timeline.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given artifact have a type that Timeline supports, and does it
|
||||
* have a positive timestamp in the supported attribute?
|
||||
* Does the given artifact have a datetime attribute?
|
||||
*
|
||||
* @param artifact The artifact to test for a supported timestamp
|
||||
*
|
||||
* @return True if this artifact has a timestamp supported by Timeline.
|
||||
*/
|
||||
public static boolean hasSupportedTimeStamp(BlackboardArtifact artifact) throws TskCoreException {
|
||||
//see if the given artifact is a supported type ...
|
||||
for (ArtifactEventType artEventType : ARTIFACT_EVENT_TYPES) {
|
||||
if (artEventType.getArtifactTypeID() == artifact.getArtifactTypeID()) {
|
||||
//... and has a non-bogus timestamp in the supported attribute
|
||||
BlackboardAttribute attribute = artifact.getAttribute(artEventType.getDateTimeAttributeType());
|
||||
if (null != attribute && attribute.getValueLong() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (BlackboardAttribute attr : artifact.getAttributes()) {
|
||||
if (attr.getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
13
Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java
Normal file → Executable file
@ -19,11 +19,15 @@
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.actions.SystemAction;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
@ -34,6 +38,8 @@ public final class ViewFileInTimelineAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ViewFileInTimelineAction.class.getName());
|
||||
|
||||
private final AbstractFile file;
|
||||
|
||||
private ViewFileInTimelineAction(AbstractFile file, String displayName) {
|
||||
@ -62,6 +68,11 @@ public final class ViewFileInTimelineAction extends AbstractAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
SystemAction.get(OpenTimelineAction.class).showFileInTimeline(file);
|
||||
try {
|
||||
SystemAction.get(OpenTimelineAction.class).showFileInTimeline(file);
|
||||
} catch (TskCoreException ex) {
|
||||
MessageNotifyUtil.Message.error("Error opening Timeline");
|
||||
logger.log(Level.SEVERE, "Error showing timeline.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 Basis Technology Corp.
|
||||
* Copyright 2015-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,27 +18,40 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomIn extends Action {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ZoomIn.class.getName());
|
||||
|
||||
private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N NON-NLS
|
||||
|
||||
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view about half as much time.",
|
||||
"ZoomIn.action.text=Zoom in"})
|
||||
"ZoomIn.action.text=Zoom in",
|
||||
"ZoomIn.errorMessage=Error zooming in."
|
||||
})
|
||||
public ZoomIn(TimeLineController controller) {
|
||||
super(Bundle.ZoomIn_action_text());
|
||||
setLongText(Bundle.ZoomIn_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_IN));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.pushZoomInTime();
|
||||
try {
|
||||
controller.pushZoomInTime();
|
||||
} catch (TskCoreException ex) {
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.ZoomIn_errorMessage()).showAndWait();
|
||||
logger.log(Level.SEVERE, "Error zooming in.", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
33
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomOut.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 Basis Technology Corp.
|
||||
* Copyright 2015-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,40 +18,61 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomOut extends Action {
|
||||
|
||||
final private static Logger logger = Logger.getLogger(ZoomOut.class.getName());
|
||||
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N NON-NLS
|
||||
|
||||
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view about 50% more time.",
|
||||
"ZoomOut.action.text=Zoom out"})
|
||||
"ZoomOut.action.text=Zoom out",
|
||||
"ZoomOut.errorMessage=Error zooming out.",
|
||||
"ZoomOut.disabledProperty.errorMessage=Error getting spanning interval."})
|
||||
public ZoomOut(TimeLineController controller) {
|
||||
super(Bundle.ZoomOut_action_text());
|
||||
setLongText(Bundle.ZoomOut_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||
setEventHandler(actionEvent -> controller.pushZoomOutTime());
|
||||
setEventHandler(actionEvent -> {
|
||||
try {
|
||||
controller.pushZoomOutTime();
|
||||
} catch (TskCoreException ex) {
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.ZoomOut_errorMessage()).showAndWait();
|
||||
logger.log(Level.SEVERE, "Error zooming out.", ex);
|
||||
}
|
||||
});
|
||||
|
||||
//disable action when the current time range already encompases the entire case.
|
||||
disabledProperty().bind(new BooleanBinding() {
|
||||
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty(), eventsModel.timeRangeProperty());
|
||||
bind(eventsModel.zoomStateProperty(), eventsModel.timeRangeProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
return eventsModel.timeRangeProperty().get().contains(eventsModel.getSpanningInterval());
|
||||
try {
|
||||
return eventsModel.getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
} catch (TskCoreException ex) {
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.ZoomOut_disabledProperty_errorMessage()).showAndWait();
|
||||
logger.log(Level.SEVERE, "Error getting spanning interval.", ex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
29
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java
Normal file → Executable file
@ -18,29 +18,40 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.FilteredEventsModel;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomToEvents extends Action {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ZoomToEvents.class.getName());
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N NON-NLS
|
||||
|
||||
@NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events",
|
||||
"ZoomToEvents.longText=Zoom out to show the nearest events."})
|
||||
"ZoomToEvents.longText=Zoom out to show the nearest events.",
|
||||
"ZoomToEvents.disabledProperty.errorMessage=Error getting spanning interval."})
|
||||
public ZoomToEvents(final TimeLineController controller) {
|
||||
super(Bundle.ZoomToEvents_action_text());
|
||||
setLongText(Bundle.ZoomToEvents_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.zoomOutToActivity();
|
||||
try {
|
||||
controller.zoomOutToActivity();
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking ZoomToEvents action", ex);
|
||||
new Alert(Alert.AlertType.ERROR, "Error zomming").showAndWait();
|
||||
}
|
||||
});
|
||||
|
||||
//disable action when the current time range already encompases the entire case.
|
||||
@ -48,13 +59,19 @@ public class ZoomToEvents extends Action {
|
||||
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty());
|
||||
bind(eventsModel.zoomStateProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
//TODO: do a db query to see if using this action will actually result in viewable events
|
||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
try {
|
||||
//TODO: do a db query to see if using this action will actually result in viewable events
|
||||
return eventsModel.getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
} catch (TskCoreException ex) {
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.ZoomToEvents_disabledProperty_errorMessage()).showAndWait();
|
||||
logger.log(Level.SEVERE, "Error getting spanning interval.", ex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,537 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-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.datamodel;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import org.joda.time.Interval;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
||||
import org.sleuthkit.autopsy.timeline.events.DBUpdatedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.HashSetFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagNameFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||
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.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* This class acts as the model for a TimelineView
|
||||
*
|
||||
* Views can register listeners on properties returned by methods.
|
||||
*
|
||||
* This class is implemented as a filtered view into an underlying
|
||||
* EventsRepository.
|
||||
*
|
||||
* TODO: as many methods as possible should cache their results so as to avoid
|
||||
* unnecessary db calls through the EventsRepository -jm
|
||||
*
|
||||
* Concurrency Policy: repo is internally synchronized, so methods that only
|
||||
* access the repo atomically do not need further synchronization
|
||||
*
|
||||
* all other member state variables should only be accessed with intrinsic lock
|
||||
* of containing FilteredEventsModel held. Many methods delegate to a task
|
||||
* submitted to the dbQueryThread executor. These methods should synchronize on
|
||||
* this object, and the tasks should too. Since the tasks execute asynchronously
|
||||
* from the invoking methods, the methods will return and release the lock for
|
||||
* the tasks to obtain.
|
||||
*
|
||||
*/
|
||||
public final class FilteredEventsModel {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(FilteredEventsModel.class.getName());
|
||||
|
||||
/**
|
||||
* time range that spans the filtered events
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<Interval> requestedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<RootFilter> requestedFilter = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper< EventTypeZoomLevel> requestedTypeZoom = new ReadOnlyObjectWrapper<>(EventTypeZoomLevel.BASE_TYPE);
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper< DescriptionLoD> requestedLOD = new ReadOnlyObjectWrapper<>(DescriptionLoD.SHORT);
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<ZoomParams> requestedZoomParamters = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
private final EventBus eventbus = new EventBus("FilteredEventsModel_EventBus"); //NON-NLS
|
||||
|
||||
/**
|
||||
* The underlying repo for events. Atomic access to repo is synchronized
|
||||
* internally, but compound access should be done with the intrinsic lock of
|
||||
* this FilteredEventsModel object
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private final EventsRepository repo;
|
||||
private final Case autoCase;
|
||||
|
||||
public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty<ZoomParams> currentStateProperty) {
|
||||
this.repo = repo;
|
||||
this.autoCase = repo.getAutoCase();
|
||||
repo.getDatasourcesMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
|
||||
DataSourceFilter dataSourceFilter = new DataSourceFilter(change.getValueAdded(), change.getKey());
|
||||
RootFilter rootFilter = filterProperty().get();
|
||||
rootFilter.getDataSourcesFilter().addSubFilter(dataSourceFilter);
|
||||
requestedFilter.set(rootFilter.copyOf());
|
||||
});
|
||||
repo.getHashSetMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
|
||||
HashSetFilter hashSetFilter = new HashSetFilter(change.getValueAdded(), change.getKey());
|
||||
RootFilter rootFilter = filterProperty().get();
|
||||
rootFilter.getHashHitsFilter().addSubFilter(hashSetFilter);
|
||||
requestedFilter.set(rootFilter.copyOf());
|
||||
});
|
||||
repo.getTagNames().addListener((ListChangeListener.Change<? extends TagName> c) -> {
|
||||
RootFilter rootFilter = filterProperty().get();
|
||||
TagsFilter tagsFilter = rootFilter.getTagsFilter();
|
||||
repo.syncTagsFilter(tagsFilter);
|
||||
requestedFilter.set(rootFilter.copyOf());
|
||||
});
|
||||
requestedFilter.set(getDefaultFilter());
|
||||
|
||||
//TODO: use bindings to keep these in sync? -jm
|
||||
requestedZoomParamters.addListener((Observable observable) -> {
|
||||
final ZoomParams zoomParams = requestedZoomParamters.get();
|
||||
|
||||
if (zoomParams != null) {
|
||||
synchronized (FilteredEventsModel.this) {
|
||||
requestedTypeZoom.set(zoomParams.getTypeZoomLevel());
|
||||
requestedFilter.set(zoomParams.getFilter());
|
||||
requestedTimeRange.set(zoomParams.getTimeRange());
|
||||
requestedLOD.set(zoomParams.getDescriptionLOD());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
requestedZoomParamters.bind(currentStateProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Readonly observable property for the current ZoomParams
|
||||
*
|
||||
* @return A readonly observable property for the current ZoomParams.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<ZoomParams> zoomParametersProperty() {
|
||||
return requestedZoomParamters.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current ZoomParams
|
||||
*
|
||||
* @return The current ZoomParams
|
||||
*/
|
||||
synchronized public ZoomParams getZoomParamaters() {
|
||||
return requestedZoomParamters.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a read only view of the time range currently in view.
|
||||
*
|
||||
* @return A read only view of the time range currently in view.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<Interval> timeRangeProperty() {
|
||||
if (requestedTimeRange.get() == null) {
|
||||
requestedTimeRange.set(getSpanningInterval());
|
||||
}
|
||||
return requestedTimeRange.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<DescriptionLoD> descriptionLODProperty() {
|
||||
return requestedLOD.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<RootFilter> filterProperty() {
|
||||
return requestedFilter.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<EventTypeZoomLevel> eventTypeZoomProperty() {
|
||||
return requestedTypeZoom.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* The time range currently in view.
|
||||
*
|
||||
* @return The time range currently in view.
|
||||
*/
|
||||
synchronized public Interval getTimeRange() {
|
||||
return timeRangeProperty().get();
|
||||
}
|
||||
|
||||
synchronized public DescriptionLoD getDescriptionLOD() {
|
||||
return requestedLOD.get();
|
||||
}
|
||||
|
||||
synchronized public RootFilter getFilter() {
|
||||
return requestedFilter.get();
|
||||
}
|
||||
|
||||
synchronized public EventTypeZoomLevel getEventTypeZoom() {
|
||||
return requestedTypeZoom.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the default filter used at startup
|
||||
*/
|
||||
public RootFilter getDefaultFilter() {
|
||||
DataSourcesFilter dataSourcesFilter = new DataSourcesFilter();
|
||||
|
||||
repo.getDatasourcesMap().entrySet().stream().forEach((Map.Entry<Long, String> t) -> {
|
||||
DataSourceFilter dataSourceFilter = new DataSourceFilter(t.getValue(), t.getKey());
|
||||
dataSourceFilter.setSelected(Boolean.TRUE);
|
||||
dataSourcesFilter.addSubFilter(dataSourceFilter);
|
||||
});
|
||||
|
||||
HashHitsFilter hashHitsFilter = new HashHitsFilter();
|
||||
repo.getHashSetMap().entrySet().stream().forEach((Map.Entry<Long, String> t) -> {
|
||||
HashSetFilter hashSetFilter = new HashSetFilter(t.getValue(), t.getKey());
|
||||
hashSetFilter.setSelected(Boolean.TRUE);
|
||||
hashHitsFilter.addSubFilter(hashSetFilter);
|
||||
});
|
||||
|
||||
TagsFilter tagsFilter = new TagsFilter();
|
||||
repo.getTagNames().stream().forEach(t -> {
|
||||
TagNameFilter tagNameFilter = new TagNameFilter(t, autoCase);
|
||||
tagNameFilter.setSelected(Boolean.TRUE);
|
||||
tagsFilter.addSubFilter(tagNameFilter);
|
||||
});
|
||||
return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, Collections.emptySet());
|
||||
}
|
||||
|
||||
public Interval getBoundingEventsInterval() {
|
||||
return repo.getBoundingEventsInterval(zoomParametersProperty().get().getTimeRange(), zoomParametersProperty().get().getFilter());
|
||||
}
|
||||
|
||||
public SingleEvent getEventById(Long eventID) {
|
||||
return repo.getEventById(eventID);
|
||||
}
|
||||
|
||||
public Set<SingleEvent> getEventsById(Collection<Long> eventIDs) {
|
||||
return repo.getEventsById(eventIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a count of tagnames applied to the given event ids as a map from
|
||||
* tagname displayname to count of tag applications
|
||||
*
|
||||
* @param eventIDsWithTags the event ids to get the tag counts map for
|
||||
*
|
||||
* @return a map from tagname displayname to count of applications
|
||||
*/
|
||||
public Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) {
|
||||
return repo.getTagCountsByTagName(eventIDsWithTags);
|
||||
}
|
||||
|
||||
public List<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||
final Interval overlap;
|
||||
final RootFilter intersect;
|
||||
synchronized (this) {
|
||||
overlap = getSpanningInterval().overlap(timeRange);
|
||||
intersect = requestedFilter.get().copyOf();
|
||||
}
|
||||
intersect.getSubFilters().add(filter);
|
||||
return repo.getEventIDs(overlap, intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
public List<CombinedEvent> getCombinedEvents() {
|
||||
return repo.getCombinedEvents(requestedTimeRange.get(), requestedFilter.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* return the number of events that pass the requested filter and are within
|
||||
* the given time range.
|
||||
*
|
||||
* NOTE: this method does not change the requested time range
|
||||
*
|
||||
* @param timeRange
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<EventType, Long> getEventCounts(Interval timeRange) {
|
||||
|
||||
final RootFilter filter;
|
||||
final EventTypeZoomLevel typeZoom;
|
||||
synchronized (this) {
|
||||
filter = requestedFilter.get();
|
||||
typeZoom = requestedTypeZoom.get();
|
||||
}
|
||||
return repo.countEvents(new ZoomParams(timeRange, typeZoom, filter, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the smallest interval spanning all the events from the
|
||||
* repository, ignoring any filters or requested ranges
|
||||
*/
|
||||
public Interval getSpanningInterval() {
|
||||
return new Interval(getMinTime() * 1000, 1000 + getMaxTime() * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the smallest interval spanning all the given events
|
||||
*/
|
||||
public Interval getSpanningInterval(Collection<Long> eventIDs) {
|
||||
return repo.getSpanningInterval(eventIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time (in seconds from unix epoch) of the absolutely first
|
||||
* event available from the repository, ignoring any filters or
|
||||
* requested ranges
|
||||
*/
|
||||
public Long getMinTime() {
|
||||
return repo.getMinTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time (in seconds from unix epoch) of the absolutely last
|
||||
* event available from the repository, ignoring any filters or
|
||||
* requested ranges
|
||||
*/
|
||||
public Long getMaxTime() {
|
||||
return repo.getMaxTime();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return a list of event clusters at the requested zoom levels that are
|
||||
* within the requested time range and pass the requested filter
|
||||
*/
|
||||
public List<EventStripe> getEventStripes() {
|
||||
final Interval range;
|
||||
final RootFilter filter;
|
||||
final EventTypeZoomLevel zoom;
|
||||
final DescriptionLoD lod;
|
||||
synchronized (this) {
|
||||
range = requestedTimeRange.get();
|
||||
filter = requestedFilter.get();
|
||||
zoom = requestedTypeZoom.get();
|
||||
lod = requestedLOD.get();
|
||||
}
|
||||
return repo.getEventStripes(new ZoomParams(range, zoom, filter, lod));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param params
|
||||
*
|
||||
* @return a list of aggregated events that are within the requested time
|
||||
* range and pass the requested filter, using the given aggregation
|
||||
* to control the grouping of events
|
||||
*/
|
||||
public List<EventStripe> getEventStripes(ZoomParams params) {
|
||||
return repo.getEventStripes(params);
|
||||
}
|
||||
|
||||
synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) {
|
||||
ContentTag contentTag = evt.getAddedTag();
|
||||
Content content = contentTag.getContent();
|
||||
Set<Long> updatedEventIDs = repo.addTag(content.getId(), null, contentTag, null);
|
||||
return postTagsAdded(updatedEventIDs);
|
||||
}
|
||||
|
||||
synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) {
|
||||
BlackboardArtifactTag artifactTag = evt.getAddedTag();
|
||||
BlackboardArtifact artifact = artifactTag.getArtifact();
|
||||
Set<Long> updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag, null);
|
||||
return postTagsAdded(updatedEventIDs);
|
||||
}
|
||||
|
||||
synchronized public boolean handleContentTagDeleted(ContentTagDeletedEvent evt) {
|
||||
DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
try {
|
||||
Content content = autoCase.getSleuthkitCase().getContentById(deletedTagInfo.getContentID());
|
||||
boolean tagged = autoCase.getServices().getTagsManager().getContentTagsByContent(content).isEmpty() == false;
|
||||
Set<Long> updatedEventIDs = repo.deleteTag(content.getId(), null, deletedTagInfo.getTagID(), tagged);
|
||||
return postTagsDeleted(updatedEventIDs);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "unable to determine tagged status of content.", ex); //NON-NLS
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized public boolean handleArtifactTagDeleted(BlackBoardArtifactTagDeletedEvent evt) {
|
||||
DeletedBlackboardArtifactTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
try {
|
||||
BlackboardArtifact artifact = autoCase.getSleuthkitCase().getBlackboardArtifact(deletedTagInfo.getArtifactID());
|
||||
boolean tagged = autoCase.getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact).isEmpty() == false;
|
||||
Set<Long> updatedEventIDs = repo.deleteTag(artifact.getObjectID(), artifact.getArtifactID(), deletedTagInfo.getTagID(), tagged);
|
||||
return postTagsDeleted(updatedEventIDs);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "unable to determine tagged status of artifact.", ex); //NON-NLS
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @param file The AbstractFile to get derived event IDs
|
||||
* for.
|
||||
* @param includeDerivedArtifacts If true, also get event IDs for events
|
||||
* derived from artifacts derived form this
|
||||
* file. If false, only gets events derived
|
||||
* directly from this file (file system
|
||||
* timestamps).
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given file.
|
||||
*/
|
||||
public List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) {
|
||||
return repo.getEventIDsForFile(file, includeDerivedArtifacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to get derived event IDs for.
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given artifact.
|
||||
*/
|
||||
public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
|
||||
return repo.getEventIDsForArtifact(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a TagsAddedEvent to all registered subscribers, if the given set of
|
||||
* updated event IDs is not empty.
|
||||
*
|
||||
* @param updatedEventIDs The set of event ids to be included in the
|
||||
* TagsAddedEvent.
|
||||
*
|
||||
* @return True if an event was posted.
|
||||
*/
|
||||
private boolean postTagsAdded(Set<Long> updatedEventIDs) {
|
||||
boolean tagsUpdated = !updatedEventIDs.isEmpty();
|
||||
if (tagsUpdated) {
|
||||
eventbus.post(new TagsAddedEvent(updatedEventIDs));
|
||||
}
|
||||
return tagsUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a TagsDeletedEvent to all registered subscribers, if the given set
|
||||
* of updated event IDs is not empty.
|
||||
*
|
||||
* @param updatedEventIDs The set of event ids to be included in the
|
||||
* TagsDeletedEvent.
|
||||
*
|
||||
* @return True if an event was posted.
|
||||
*/
|
||||
private boolean postTagsDeleted(Set<Long> updatedEventIDs) {
|
||||
boolean tagsUpdated = !updatedEventIDs.isEmpty();
|
||||
if (tagsUpdated) {
|
||||
eventbus.post(new TagsDeletedEvent(updatedEventIDs));
|
||||
}
|
||||
return tagsUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given object to receive events.
|
||||
*
|
||||
* @param o The object to register. Must implement public methods annotated
|
||||
* with Subscribe.
|
||||
*/
|
||||
synchronized public void registerForEvents(Object o) {
|
||||
eventbus.register(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register the given object, so it no longer receives events.
|
||||
*
|
||||
* @param o The object to un-register.
|
||||
*/
|
||||
synchronized public void unRegisterForEvents(Object o) {
|
||||
eventbus.unregister(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a DBUpdatedEvent to all registered subscribers.
|
||||
*/
|
||||
public void postDBUpdated() {
|
||||
eventbus.post(new DBUpdatedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a RefreshRequestedEvent to all registered subscribers.
|
||||
*/
|
||||
public void postRefreshRequest() {
|
||||
eventbus.post(new RefreshRequestedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Post an AutopsyEvent received from another event distribution system
|
||||
* locally to all registered subscribers.
|
||||
*/
|
||||
public void postAutopsyEventLocally(AutopsyEvent event) {
|
||||
eventbus.post(event);
|
||||
}
|
||||
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-16 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.datamodel.eventtype;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface ArtifactEventType extends EventType {
|
||||
|
||||
public static final Logger LOGGER = Logger.getLogger(ArtifactEventType.class.getName());
|
||||
|
||||
/**
|
||||
* Get the artifact type this event type is derived from.
|
||||
*
|
||||
* @return The artifact type this event type is derived from.
|
||||
*/
|
||||
public BlackboardArtifact.Type getArtifactType();
|
||||
|
||||
/**
|
||||
* The attribute type this event type is derived from.
|
||||
*
|
||||
* @return The attribute type this event type is derived from.
|
||||
*/
|
||||
public BlackboardAttribute.Type getDateTimeAttributeType();
|
||||
|
||||
/**
|
||||
* Get the ID of the the artifact type that this EventType is derived from.
|
||||
*
|
||||
* @return the ID of the the artifact type that this EventType is derived
|
||||
* from.
|
||||
*/
|
||||
public default int getArtifactTypeID() {
|
||||
return getArtifactType().getTypeID();
|
||||
}
|
||||
|
||||
/**
|
||||
* given an artifact, pull out the time stamp, and compose the descriptions.
|
||||
* Each implementation of ArtifactEventType needs to implement
|
||||
* parseAttributesHelper() as hook for buildEventDescription(org.sleuthkit.datamodel.BlackboardArtifact)
|
||||
* to invoke. Most subtypes can use this default implementation.
|
||||
*
|
||||
* @param artf
|
||||
*
|
||||
* @return an AttributeEventDescription containing the timestamp
|
||||
* and description information
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
default AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException {
|
||||
final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttributeType());
|
||||
|
||||
long time = dateTimeAttr.getValueLong();
|
||||
String shortDescription = getShortExtractor().apply(artf);
|
||||
String medDescription = shortDescription + " : " + getMedExtractor().apply(artf);
|
||||
String fullDescription = medDescription + " : " + getFullExtractor().apply(artf);
|
||||
return new AttributeEventDescription(time, shortDescription, medDescription, fullDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a function from an artifact to a String to use as part of the
|
||||
* full event description
|
||||
*/
|
||||
Function<BlackboardArtifact, String> getFullExtractor();
|
||||
|
||||
/**
|
||||
* @return a function from an artifact to a String to use as part of the
|
||||
* medium event description
|
||||
*/
|
||||
Function<BlackboardArtifact, String> getMedExtractor();
|
||||
|
||||
/**
|
||||
* @return a function from an artifact to a String to use as part of the
|
||||
* short event description
|
||||
*/
|
||||
Function<BlackboardArtifact, String> getShortExtractor();
|
||||
|
||||
/**
|
||||
* bundles the per event information derived from a BlackBoard Artifact into
|
||||
* one object. Primarily used to have a single return value for
|
||||
* ArtifactEventType#buildEventDescription(ArtifactEventType, BlackboardArtifact).
|
||||
*/
|
||||
static class AttributeEventDescription {
|
||||
|
||||
final private long time;
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public String getShortDescription() {
|
||||
return shortDescription;
|
||||
}
|
||||
|
||||
public String getMedDescription() {
|
||||
return medDescription;
|
||||
}
|
||||
|
||||
public String getFullDescription() {
|
||||
return fullDescription;
|
||||
}
|
||||
|
||||
final private String shortDescription;
|
||||
|
||||
final private String medDescription;
|
||||
|
||||
final private String fullDescription;
|
||||
|
||||
public AttributeEventDescription(long time, String shortDescription,
|
||||
String medDescription,
|
||||
String fullDescription) {
|
||||
this.time = time;
|
||||
this.shortDescription = shortDescription;
|
||||
this.medDescription = medDescription;
|
||||
this.fullDescription = fullDescription;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a AttributeEventDescription derived from a BlackboardArtifact. This
|
||||
* is a template method that relies on each ArtifactEventType's
|
||||
* implementation of ArtifactEventType#parseAttributesHelper() to know how
|
||||
* to go from BlackboardAttributes to the event description.
|
||||
*
|
||||
* @param type
|
||||
* @param artf the BlackboardArtifact to derive the event description from
|
||||
*
|
||||
* @return an AttributeEventDescription derived from the given artifact, if
|
||||
* the given artifact has no timestamp
|
||||
*
|
||||
* @throws TskCoreException is there is a problem accessing the blackboard
|
||||
* data
|
||||
*/
|
||||
static public AttributeEventDescription buildEventDescription(ArtifactEventType type, BlackboardArtifact artf) throws TskCoreException {
|
||||
//if we got passed an artifact that doesn't correspond to the type of the event,
|
||||
//something went very wrong. throw an exception.
|
||||
if (type.getArtifactTypeID() != artf.getArtifactTypeID()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (artf.getAttribute(type.getDateTimeAttributeType()) == null) {
|
||||
LOGGER.log(Level.WARNING, "Artifact {0} has no date/time attribute, skipping it.", artf.getArtifactID()); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
//use the hook provided by this subtype implementation
|
||||
return type.parseAttributesHelper(artf);
|
||||
}
|
||||
|
||||
static class AttributeExtractor implements Function<BlackboardArtifact, String> {
|
||||
|
||||
public String apply(BlackboardArtifact artf) {
|
||||
return Optional.ofNullable(getAttributeSafe(artf, attributeType))
|
||||
.map(BlackboardAttribute::getDisplayString)
|
||||
.map(StringUtils::defaultString)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private final BlackboardAttribute.Type attributeType;
|
||||
|
||||
public AttributeExtractor(BlackboardAttribute.Type attribute) {
|
||||
this.attributeType = attribute;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class EmptyExtractor implements Function<BlackboardArtifact, String> {
|
||||
|
||||
@Override
|
||||
public String apply(BlackboardArtifact t) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static BlackboardAttribute getAttributeSafe(BlackboardArtifact artf, BlackboardAttribute.Type attrType) {
|
||||
try {
|
||||
return artf.getAttribute(attrType);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting attribute from artifact {0}.", artf.getArtifactID()), ex); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 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.datamodel.eventtype;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javafx.scene.image.Image;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
|
||||
/**
|
||||
* RootTypes are event types that have no super type.
|
||||
*/
|
||||
public enum BaseTypes implements EventType {
|
||||
|
||||
FILE_SYSTEM(NbBundle.getMessage(BaseTypes.class, "BaseTypes.fileSystem.name"), "blue-document.png") { // NON-NLS
|
||||
|
||||
@Override
|
||||
public List<? extends EventType> getSubTypes() {
|
||||
return Arrays.asList(FileSystemTypes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return FileSystemTypes.valueOf(string);
|
||||
}
|
||||
},
|
||||
WEB_ACTIVITY(NbBundle.getMessage(BaseTypes.class, "BaseTypes.webActivity.name"), "web-file.png") { // NON-NLS
|
||||
|
||||
@Override
|
||||
public List<? extends EventType> getSubTypes() {
|
||||
return Arrays.asList(WebTypes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return WebTypes.valueOf(string);
|
||||
}
|
||||
},
|
||||
MISC_TYPES(NbBundle.getMessage(BaseTypes.class, "BaseTypes.miscTypes.name"), "block.png") { // NON-NLS
|
||||
|
||||
@Override
|
||||
public List<? extends EventType> getSubTypes() {
|
||||
return Arrays.asList(MiscTypes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return MiscTypes.valueOf(string);
|
||||
}
|
||||
};
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private final String iconBase;
|
||||
|
||||
private final Image image;
|
||||
|
||||
@Override
|
||||
public Image getFXImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconBase() {
|
||||
return iconBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventTypeZoomLevel getZoomLevel() {
|
||||
return EventTypeZoomLevel.BASE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
private BaseTypes(String displayName, String iconBase) {
|
||||
this.displayName = displayName;
|
||||
this.iconBase = iconBase;
|
||||
this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSuperType() {
|
||||
return RootEventType.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return BaseTypes.valueOf(string);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
BaseTypes.fileSystem.name=File System
|
||||
BaseTypes.webActivity.name=Web Activity
|
||||
BaseTypes.miscTypes.name=Misc Types
|
||||
FileSystemTypes.fileModified.name=File Modified
|
||||
FileSystemTypes.fileAccessed.name=File Accessed
|
||||
FileSystemTypes.fileCreated.name=File Created
|
||||
FileSystemTypes.fileChanged.name=File Changed
|
||||
MiscTypes.message.name=Messages
|
||||
MiscTypes.GPSRoutes.name=GPS Routes
|
||||
MiscTypes.GPSTrackpoint.name=Location History
|
||||
MiscTypes.Calls.name=Calls
|
||||
MiscTypes.Email.name=Email
|
||||
MiscTypes.recentDocuments.name=Recent Documents
|
||||
MiscTypes.installedPrograms.name=Installed Programs
|
||||
MiscTypes.exif.name=Exif
|
||||
MiscTypes.devicesAttached.name=Devices Attached
|
||||
RootEventType.eventTypes.name=Event Types
|
||||
WebTypes.webDownloads.name=Web Downloads
|
||||
WebTypes.webCookies.name=Web Cookies
|
||||
WebTypes.webBookmarks.name=Web Bookmarks
|
||||
WebTypes.webHistory.name=Web History
|
||||
WebTypes.webSearch.name=Web Searches
|
@ -1,22 +0,0 @@
|
||||
BaseTypes.fileSystem.name=\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0
|
||||
BaseTypes.miscTypes.name=\u305D\u306E\u4ED6\u30BF\u30A4\u30D7
|
||||
BaseTypes.webActivity.name=\u30A6\u30A7\u30D6\u30A2\u30AF\u30C6\u30A3\u30D3\u30C6\u30A3
|
||||
FileSystemTypes.fileAccessed.name=\u30A2\u30AF\u30BB\u30B9\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB
|
||||
FileSystemTypes.fileChanged.name=\u5909\u66F4\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB
|
||||
FileSystemTypes.fileCreated.name=\u4F5C\u6210\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB
|
||||
FileSystemTypes.fileModified.name=\u4FEE\u6B63\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB
|
||||
MiscTypes.Calls.name=\u30B3\u30FC\u30EB
|
||||
MiscTypes.devicesAttached.name=\u63A5\u7D9A\u3055\u308C\u3066\u3044\u308B\u6A5F\u5668
|
||||
MiscTypes.Email.name=Email
|
||||
MiscTypes.exif.name=Exif
|
||||
MiscTypes.GPSRoutes.name=GPS\u30EB\u30FC\u30C8
|
||||
MiscTypes.GPSTrackpoint.name=\u4F4D\u7F6E\u60C5\u5831\u5C65\u6B74
|
||||
MiscTypes.installedPrograms.name=\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u308B\u30D7\u30ED\u30B0\u30E9\u30E0
|
||||
MiscTypes.message.name=\u30E1\u30C3\u30BB\u30FC\u30B8
|
||||
MiscTypes.recentDocuments.name=\u6700\u8FD1\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8
|
||||
RootEventType.eventTypes.name=\u30A4\u30D9\u30F3\u30C8\u30BF\u30A4\u30D7
|
||||
WebTypes.webBookmarks.name=\u30A6\u30A7\u30D6\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF
|
||||
WebTypes.webCookies.name=\u30A6\u30A7\u30D6\u30AF\u30C3\u30AD\u30FC
|
||||
WebTypes.webDownloads.name=\u30A6\u30A7\u30D6\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||
WebTypes.webHistory.name=\u30A6\u30A7\u30D6\u5C65\u6B74
|
||||
WebTypes.webSearch.name=\u30A6\u30A7\u30D6\u691C\u7D22
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 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.datamodel.eventtype;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
|
||||
/**
|
||||
* An Event Type represents a distinct kind of event ie file system or web
|
||||
* activity. An EventType may have an optional super-type and 0 or more
|
||||
* subtypes, allowing events to be organized in a type hierarchy.
|
||||
*/
|
||||
public interface EventType {
|
||||
|
||||
final static List<? extends EventType> allTypes = RootEventType.getInstance().getSubTypesRecusive();
|
||||
|
||||
static Comparator<EventType> getComparator() {
|
||||
return Comparator.comparing(EventType.allTypes::indexOf);
|
||||
|
||||
}
|
||||
|
||||
default BaseTypes getBaseType() {
|
||||
if (this instanceof BaseTypes) {
|
||||
return (BaseTypes) this;
|
||||
} else {
|
||||
return getSuperType().getBaseType();
|
||||
}
|
||||
}
|
||||
|
||||
default List<? extends EventType> getSubTypesRecusive() {
|
||||
ArrayList<EventType> flatList = new ArrayList<>();
|
||||
|
||||
for (EventType et : getSubTypes()) {
|
||||
flatList.add(et);
|
||||
flatList.addAll(et.getSubTypesRecusive());
|
||||
}
|
||||
return flatList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the color used to represent this event type visually
|
||||
*/
|
||||
default Color getColor() {
|
||||
|
||||
Color baseColor = this.getSuperType().getColor();
|
||||
int siblings = getSuperType().getSiblingTypes().stream().max((
|
||||
EventType t, EventType t1)
|
||||
-> Integer.compare(t.getSubTypes().size(), t1.getSubTypes().size()))
|
||||
.get().getSubTypes().size() + 1;
|
||||
int superSiblings = this.getSuperType().getSiblingTypes().size();
|
||||
|
||||
double offset = (360.0 / superSiblings) / siblings;
|
||||
final Color deriveColor = baseColor.deriveColor(ordinal() * offset, 1, 1, 1);
|
||||
|
||||
return Color.hsb(deriveColor.getHue(), deriveColor.getSaturation(), deriveColor.getBrightness());
|
||||
|
||||
}
|
||||
|
||||
default List<? extends EventType> getSiblingTypes() {
|
||||
return this.getSuperType().getSubTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the super type of this event
|
||||
*/
|
||||
public EventType getSuperType();
|
||||
|
||||
public EventTypeZoomLevel getZoomLevel();
|
||||
|
||||
/**
|
||||
* @return a list of event types, one for each subtype of this eventype, or
|
||||
* an empty list if this event type has no subtypes
|
||||
*/
|
||||
public List<? extends EventType> getSubTypes();
|
||||
|
||||
/*
|
||||
* return the name of the icon file for this type, it will be resolved in
|
||||
* the org/sleuthkit/autopsy/timeline/images
|
||||
*/
|
||||
public String getIconBase();
|
||||
|
||||
public String getDisplayName();
|
||||
|
||||
public EventType getSubType(String string);
|
||||
|
||||
public Image getFXImage();
|
||||
|
||||
public int ordinal();
|
||||
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 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.datamodel.eventtype;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.scene.image.Image;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum FileSystemTypes implements EventType {
|
||||
|
||||
FILE_MODIFIED(NbBundle.getMessage(FileSystemTypes.class, "FileSystemTypes.fileModified.name"), "blue-document-attribute-m.png"), // NON-NLS
|
||||
FILE_ACCESSED(NbBundle.getMessage(FileSystemTypes.class, "FileSystemTypes.fileAccessed.name"), "blue-document-attribute-a.png"), // NON-NLS
|
||||
FILE_CREATED(NbBundle.getMessage(FileSystemTypes.class, "FileSystemTypes.fileCreated.name"), "blue-document-attribute-b.png"), // NON-NLS
|
||||
FILE_CHANGED(NbBundle.getMessage(FileSystemTypes.class, "FileSystemTypes.fileChanged.name"), "blue-document-attribute-c.png"); // NON-NLS
|
||||
|
||||
private final String iconBase;
|
||||
|
||||
private final Image image;
|
||||
|
||||
@Override
|
||||
public Image getFXImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconBase() {
|
||||
return iconBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventTypeZoomLevel getZoomLevel() {
|
||||
return EventTypeZoomLevel.SUB_TYPE;
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return FileSystemTypes.valueOf(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSuperType() {
|
||||
return BaseTypes.FILE_SYSTEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends EventType> getSubTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private FileSystemTypes(String displayName, String iconBase) {
|
||||
this.displayName = displayName;
|
||||
this.iconBase = iconBase;
|
||||
this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-16 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.datamodel.eventtype;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import javafx.scene.image.Image;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import static org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType.getAttributeSafe;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum MiscTypes implements EventType, ArtifactEventType {
|
||||
|
||||
MESSAGE(NbBundle.getMessage(MiscTypes.class, "MiscTypes.message.name"), "message.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_MESSAGE),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE)),
|
||||
artf -> {
|
||||
final BlackboardAttribute dir = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DIRECTION));
|
||||
final BlackboardAttribute readStatus = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_READ_STATUS));
|
||||
final BlackboardAttribute name = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME));
|
||||
final BlackboardAttribute phoneNumber = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
|
||||
final BlackboardAttribute subject = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SUBJECT));
|
||||
List<String> asList = Arrays.asList(stringValueOf(dir), stringValueOf(readStatus), name != null || phoneNumber != null ? toFrom(dir) : "", stringValueOf(name != null ? name : phoneNumber), (subject == null ? "" : stringValueOf(subject)));
|
||||
return StringUtils.join(asList, " ");
|
||||
},
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT))),
|
||||
GPS_ROUTE(NbBundle.getMessage(MiscTypes.class, "MiscTypes.GPSRoutes.name"), "gps-search.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_GPS_ROUTE),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION)),
|
||||
artf -> {
|
||||
final BlackboardAttribute latStart = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START));
|
||||
final BlackboardAttribute longStart = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START));
|
||||
final BlackboardAttribute latEnd = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END));
|
||||
final BlackboardAttribute longEnd = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END));
|
||||
return String.format("from %1$s %2$s to %3$s %4$s", stringValueOf(latStart), stringValueOf(longStart), stringValueOf(latEnd), stringValueOf(longEnd)); // NON-NLS
|
||||
}),
|
||||
GPS_TRACKPOINT(NbBundle.getMessage(MiscTypes.class, "MiscTypes.GPSTrackpoint.name"), "gps-trackpoint.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)),
|
||||
artf -> {
|
||||
final BlackboardAttribute longitude = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE));
|
||||
final BlackboardAttribute latitude = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE));
|
||||
return stringValueOf(latitude) + " " + stringValueOf(longitude); // NON-NLS
|
||||
},
|
||||
new EmptyExtractor()),
|
||||
CALL_LOG(NbBundle.getMessage(MiscTypes.class, "MiscTypes.Calls.name"), "calllog.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_CALLLOG),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DIRECTION))),
|
||||
EMAIL(NbBundle.getMessage(MiscTypes.class, "MiscTypes.Email.name"), "mail-icon-16.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_EMAIL_MSG),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_SENT),
|
||||
artf -> {
|
||||
final BlackboardAttribute emailFrom = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_FROM));
|
||||
final BlackboardAttribute emailTo = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_TO));
|
||||
return stringValueOf(emailFrom) + " to " + stringValueOf(emailTo); // NON-NLS
|
||||
},
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SUBJECT)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN))),
|
||||
RECENT_DOCUMENTS(NbBundle.getMessage(MiscTypes.class, "MiscTypes.recentDocuments.name"), "recent_docs.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_RECENT_OBJECT),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH)).andThen(
|
||||
(String t) -> (StringUtils.substringBeforeLast(StringUtils.substringBeforeLast(t, "\\"), "\\"))),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH)).andThen(
|
||||
(String t) -> StringUtils.substringBeforeLast(t, "\\")),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))) {
|
||||
|
||||
@Override
|
||||
public AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException {
|
||||
final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttributeType());
|
||||
|
||||
long time = dateTimeAttr.getValueLong();
|
||||
|
||||
//Non-default description construction
|
||||
String shortDescription = getShortExtractor().apply(artf);
|
||||
String medDescription = getMedExtractor().apply(artf);
|
||||
String fullDescription = getFullExtractor().apply(artf);
|
||||
|
||||
return new AttributeEventDescription(time, shortDescription, medDescription, fullDescription);
|
||||
}
|
||||
},
|
||||
INSTALLED_PROGRAM(NbBundle.getMessage(MiscTypes.class, "MiscTypes.installedPrograms.name"), "programs.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_INSTALLED_PROG),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)),
|
||||
new EmptyExtractor(),
|
||||
new EmptyExtractor()),
|
||||
EXIF(NbBundle.getMessage(MiscTypes.class, "MiscTypes.exif.name"), "camera-icon-16.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_METADATA_EXIF),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL)),
|
||||
artf -> {
|
||||
try {
|
||||
AbstractFile file = artf.getSleuthkitCase().getAbstractFileById(artf.getObjectID());
|
||||
if (file != null) {
|
||||
return file.getName();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Exif event type failed to look up backing file name", ex); //NON-NLS
|
||||
}
|
||||
return "error loading file name";
|
||||
}),
|
||||
DEVICES_ATTACHED(NbBundle.getMessage(MiscTypes.class, "MiscTypes.devicesAttached.name"), "usb_devices.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_ID)));
|
||||
|
||||
static public String stringValueOf(BlackboardAttribute attr) {
|
||||
return Optional.ofNullable(attr)
|
||||
.map(BlackboardAttribute::getDisplayString)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public static String toFrom(BlackboardAttribute dir) {
|
||||
if (dir == null) {
|
||||
return "";
|
||||
} else {
|
||||
switch (dir.getDisplayString()) {
|
||||
case "Incoming": // NON-NLS
|
||||
return "from"; // NON-NLS
|
||||
case "Outgoing": // NON-NLS
|
||||
return "to"; // NON-NLS
|
||||
default:
|
||||
return ""; // NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final BlackboardAttribute.Type dateTimeAttributeType;
|
||||
|
||||
private final String iconBase;
|
||||
|
||||
private final Image image;
|
||||
|
||||
@Override
|
||||
public Image getFXImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
private final Function<BlackboardArtifact, String> longExtractor;
|
||||
|
||||
private final Function<BlackboardArtifact, String> medExtractor;
|
||||
|
||||
private final Function<BlackboardArtifact, String> shortExtractor;
|
||||
|
||||
@Override
|
||||
public Function<BlackboardArtifact, String> getFullExtractor() {
|
||||
return longExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<BlackboardArtifact, String> getMedExtractor() {
|
||||
return medExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<BlackboardArtifact, String> getShortExtractor() {
|
||||
return shortExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlackboardAttribute.Type getDateTimeAttributeType() {
|
||||
return dateTimeAttributeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventTypeZoomLevel getZoomLevel() {
|
||||
return EventTypeZoomLevel.SUB_TYPE;
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private final BlackboardArtifact.Type artifactType;
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconBase() {
|
||||
return iconBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return MiscTypes.valueOf(string);
|
||||
}
|
||||
|
||||
private MiscTypes(String displayName, String iconBase, BlackboardArtifact.Type artifactType,
|
||||
BlackboardAttribute.Type dateTimeAttributeType,
|
||||
Function<BlackboardArtifact, String> shortExtractor,
|
||||
Function<BlackboardArtifact, String> medExtractor,
|
||||
Function<BlackboardArtifact, String> longExtractor) {
|
||||
this.displayName = displayName;
|
||||
this.iconBase = iconBase;
|
||||
this.artifactType = artifactType;
|
||||
this.dateTimeAttributeType = dateTimeAttributeType;
|
||||
this.shortExtractor = shortExtractor;
|
||||
this.medExtractor = medExtractor;
|
||||
this.longExtractor = longExtractor;
|
||||
this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSuperType() {
|
||||
return BaseTypes.MISC_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends EventType> getSubTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlackboardArtifact.Type getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 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.datamodel.eventtype;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
|
||||
/**
|
||||
* A singleton EventType to represent the root type of all event types.
|
||||
*/
|
||||
public class RootEventType implements EventType {
|
||||
|
||||
@Override
|
||||
public List<RootEventType> getSiblingTypes() {
|
||||
return Collections.singletonList(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventTypeZoomLevel getZoomLevel() {
|
||||
return EventTypeZoomLevel.ROOT_TYPE;
|
||||
}
|
||||
|
||||
private RootEventType() {
|
||||
}
|
||||
|
||||
public static RootEventType getInstance() {
|
||||
return RootEventTypeHolder.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return BaseTypes.valueOf(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ordinal() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class RootEventTypeHolder {
|
||||
|
||||
private static final RootEventType INSTANCE = new RootEventType();
|
||||
|
||||
private RootEventTypeHolder() {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return Color.hsb(359, .9, .9, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootEventType getSuperType() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BaseTypes> getSubTypes() {
|
||||
return Arrays.asList(BaseTypes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconBase() {
|
||||
throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return NbBundle.getMessage(this.getClass(), "RootEventType.eventTypes.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getFXImage() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-16 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.datamodel.eventtype;
|
||||
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import javafx.scene.image.Image;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum WebTypes implements EventType, ArtifactEventType {
|
||||
|
||||
WEB_DOWNLOADS(NbBundle.getMessage(WebTypes.class, "WebTypes.webDownloads.name"),
|
||||
"downloads.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED),
|
||||
TopPrivateDomainExtractor.getInstance(),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))) {
|
||||
|
||||
@Override
|
||||
public AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException {
|
||||
long time = artf.getAttribute(getDateTimeAttributeType()).getValueLong();
|
||||
String domain = getShortExtractor().apply(artf);
|
||||
String path = getMedExtractor().apply(artf);
|
||||
String fileName = StringUtils.substringAfterLast(path, "/");
|
||||
String url = getFullExtractor().apply(artf);
|
||||
|
||||
//TODO: review non default description construction
|
||||
String shortDescription = fileName + " from " + domain; // NON-NLS
|
||||
String medDescription = fileName + " from " + url; // NON-NLS
|
||||
String fullDescription = path + " from " + url; // NON-NLS
|
||||
return new AttributeEventDescription(time, shortDescription, medDescription, fullDescription);
|
||||
}
|
||||
},
|
||||
//TODO: review description separators
|
||||
WEB_COOKIE(NbBundle.getMessage(WebTypes.class, "WebTypes.webCookies.name"),
|
||||
"cookies.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
TopPrivateDomainExtractor.getInstance(),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE))),
|
||||
//TODO: review description separators
|
||||
WEB_BOOKMARK(NbBundle.getMessage(WebTypes.class, "WebTypes.webBookmarks.name"),
|
||||
"bookmarks.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED),
|
||||
TopPrivateDomainExtractor.getInstance(),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE))),
|
||||
//TODO: review description separators
|
||||
WEB_HISTORY(NbBundle.getMessage(WebTypes.class, "WebTypes.webHistory.name"),
|
||||
"history.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED),
|
||||
TopPrivateDomainExtractor.getInstance(),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE))),
|
||||
//TODO: review description separators
|
||||
WEB_SEARCH(NbBundle.getMessage(WebTypes.class, "WebTypes.webSearch.name"),
|
||||
"searchquery.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY),
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT)),
|
||||
TopPrivateDomainExtractor.getInstance(),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME)));
|
||||
|
||||
private final BlackboardAttribute.Type dateTimeAttributeType;
|
||||
|
||||
private final String iconBase;
|
||||
|
||||
private final Image image;
|
||||
|
||||
@Override
|
||||
public Image getFXImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlackboardAttribute.Type getDateTimeAttributeType() {
|
||||
return dateTimeAttributeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventTypeZoomLevel getZoomLevel() {
|
||||
return EventTypeZoomLevel.SUB_TYPE;
|
||||
}
|
||||
|
||||
private final Function<BlackboardArtifact, String> longExtractor;
|
||||
|
||||
private final Function<BlackboardArtifact, String> medExtractor;
|
||||
|
||||
private final Function<BlackboardArtifact, String> shortExtractor;
|
||||
|
||||
@Override
|
||||
public Function<BlackboardArtifact, String> getFullExtractor() {
|
||||
return longExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<BlackboardArtifact, String> getMedExtractor() {
|
||||
return medExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<BlackboardArtifact, String> getShortExtractor() {
|
||||
return shortExtractor;
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private final BlackboardArtifact.Type artifactType;
|
||||
|
||||
@Override
|
||||
public String getIconBase() {
|
||||
return iconBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlackboardArtifact.Type getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
|
||||
private WebTypes(String displayName, String iconBase, BlackboardArtifact.Type artifactType,
|
||||
BlackboardAttribute.Type dateTimeAttributeType,
|
||||
Function<BlackboardArtifact, String> shortExtractor,
|
||||
Function<BlackboardArtifact, String> medExtractor,
|
||||
Function<BlackboardArtifact, String> longExtractor) {
|
||||
this.displayName = displayName;
|
||||
this.iconBase = iconBase;
|
||||
this.artifactType = artifactType;
|
||||
this.dateTimeAttributeType = dateTimeAttributeType;
|
||||
this.shortExtractor = shortExtractor;
|
||||
this.medExtractor = medExtractor;
|
||||
this.longExtractor = longExtractor;
|
||||
this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSuperType() {
|
||||
return BaseTypes.WEB_ACTIVITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getSubType(String string) {
|
||||
return WebTypes.valueOf(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends EventType> getSubTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static class TopPrivateDomainExtractor extends AttributeExtractor {
|
||||
|
||||
final private static TopPrivateDomainExtractor instance = new TopPrivateDomainExtractor();
|
||||
|
||||
static TopPrivateDomainExtractor getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(BlackboardArtifact artf) {
|
||||
String domainString = StringUtils.substringBefore(super.apply(artf), "/");
|
||||
if (InternetDomainName.isValid(domainString)) {
|
||||
InternetDomainName domain = InternetDomainName.from(domainString);
|
||||
return (domain.isUnderPublicSuffix())
|
||||
? domain.topPrivateDomain().toString()
|
||||
: domain.toString();
|
||||
} else {
|
||||
return domainString;
|
||||
}
|
||||
}
|
||||
|
||||
TopPrivateDomainExtractor() {
|
||||
super(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
msgdlg.problem.text=There was a problem populating the timeline. Not all events may be present or accurate.
|
||||
progressWindow.msg.commitingDb=Committing events database
|
||||
progressWindow.msg.gatheringData=Gathering event data
|
||||
progressWindow.msg.populateMacEventsFiles=Populating MAC time events for files
|
||||
progressWindow.msg.refreshingFileTags=Refreshing file tags
|
||||
progressWindow.msg.refreshingResultTags=Refreshing result tags
|
||||
# {0} - event type
|
||||
progressWindow.populatingXevents=Populating {0} events
|
@ -1,7 +0,0 @@
|
||||
msgdlg.problem.text=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u306B\u5165\u529B\u3059\u308B\u969B\u306B\u554F\u984C\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u5168\u3066\u306E\u30A4\u30D9\u30F3\u30C8\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u6B63\u78BA\u3067\u306F\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
progressWindow.msg.commitingDb=\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u30A4\u30D9\u30F3\u30C8\u3092\u30B3\u30DF\u30C3\u30C8\u3057\u3066\u3044\u307E\u3059\u3002
|
||||
progressWindow.msg.gatheringData=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u3092\u53CE\u96C6\u4E2D
|
||||
progressWindow.msg.populateMacEventsFiles=\u30D5\u30A1\u30A4\u30EB\u306EMAC\u30BF\u30A4\u30E0\u3092\u5165\u529B\u4E2D
|
||||
progressWindow.msg.refreshingFileTags=\u30D5\u30A1\u30A4\u30EB\u30BF\u30B0\u3092\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u4E2D
|
||||
progressWindow.msg.refreshingResultTags=\u7D50\u679C\u30BF\u30B0\u3092\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u4E2D
|
||||
progressWindow.populatingXevents={0}\u30A4\u30D9\u30F3\u30C8\u3092\u5165\u529B\u4E2D
|
@ -1,727 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 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.db;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static java.util.Objects.isNull;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.concurrent.Worker;
|
||||
import javax.swing.JOptionPane;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.Interval;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.windows.WindowManager;
|
||||
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.timeline.CancellationProgressTask;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.FileSystemTypes;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagNameFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Provides higher-level public API (over EventsDB) to access events. In theory
|
||||
* this insulates the rest of the timeline module form the details of the db
|
||||
* implementation. Since there are no other implementations of the database or
|
||||
* clients of this class, and no Java Interface defined yet, in practice this
|
||||
* just delegates everything to the eventDB. Some results are also cached by
|
||||
* this layer.
|
||||
*
|
||||
* Concurrency Policy:
|
||||
*
|
||||
* Since almost everything just delegates to the EventDB, which is internally
|
||||
* synchronized, we only have to worry about rebuildRepository() which we
|
||||
* synchronize on our intrinsic lock.
|
||||
*
|
||||
*/
|
||||
public class EventsRepository {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(EventsRepository.class.getName());
|
||||
|
||||
private final Executor workerExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("eventrepository-worker-%d").build()); //NON-NLS
|
||||
private DBPopulationWorker dbWorker;
|
||||
private final EventDB eventDB;
|
||||
private final Case autoCase;
|
||||
private final FilteredEventsModel modelInstance;
|
||||
|
||||
private final LoadingCache<Object, Long> maxCache;
|
||||
private final LoadingCache<Object, Long> minCache;
|
||||
private final LoadingCache<Long, SingleEvent> idToEventCache;
|
||||
private final LoadingCache<ZoomParams, Map<EventType, Long>> eventCountsCache;
|
||||
private final LoadingCache<ZoomParams, List<EventStripe>> eventStripeCache;
|
||||
|
||||
private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap();
|
||||
private final ObservableMap<Long, String> hashSetMap = FXCollections.observableHashMap();
|
||||
private final ObservableList<TagName> tagNames = FXCollections.observableArrayList();
|
||||
|
||||
public Case getAutoCase() {
|
||||
return autoCase;
|
||||
}
|
||||
|
||||
public ObservableList<TagName> getTagNames() {
|
||||
return tagNames;
|
||||
}
|
||||
|
||||
synchronized public ObservableMap<Long, String> getDatasourcesMap() {
|
||||
return datasourcesMap;
|
||||
}
|
||||
|
||||
synchronized public ObservableMap<Long, String> getHashSetMap() {
|
||||
return hashSetMap;
|
||||
}
|
||||
|
||||
public Interval getBoundingEventsInterval(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getBoundingEventsInterval(timeRange, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a FilteredEvetns object with this repository as underlying source
|
||||
* of events
|
||||
*/
|
||||
public FilteredEventsModel getEventsModel() {
|
||||
return modelInstance;
|
||||
}
|
||||
|
||||
public EventsRepository(Case autoCase, ReadOnlyObjectProperty<ZoomParams> currentStateProperty) {
|
||||
this.autoCase = autoCase;
|
||||
//TODO: we should check that case is open, or get passed a case object/directory -jm
|
||||
this.eventDB = EventDB.getEventDB(autoCase);
|
||||
populateFilterData(autoCase.getSleuthkitCase());
|
||||
idToEventCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(5000L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build(CacheLoader.from(eventDB::getEventById));
|
||||
eventCountsCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(1000L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build(CacheLoader.from(eventDB::countEventsByType));
|
||||
eventStripeCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(1000L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES
|
||||
).build(CacheLoader.from(eventDB::getEventStripes));
|
||||
maxCache = CacheBuilder.newBuilder().build(CacheLoader.from(eventDB::getMaxTime));
|
||||
minCache = CacheBuilder.newBuilder().build(CacheLoader.from(eventDB::getMinTime));
|
||||
this.modelInstance = new FilteredEventsModel(this, currentStateProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return min time (in seconds from unix epoch)
|
||||
*/
|
||||
public Long getMaxTime() {
|
||||
return maxCache.getUnchecked("max"); // NON-NLS
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return max tie (in seconds from unix epoch)
|
||||
*/
|
||||
public Long getMinTime() {
|
||||
return minCache.getUnchecked("min"); // NON-NLS
|
||||
|
||||
}
|
||||
|
||||
public SingleEvent getEventById(Long eventID) {
|
||||
return idToEventCache.getUnchecked(eventID);
|
||||
}
|
||||
|
||||
synchronized public Set<SingleEvent> getEventsById(Collection<Long> eventIDs) {
|
||||
return eventIDs.stream()
|
||||
.map(idToEventCache::getUnchecked)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
}
|
||||
|
||||
synchronized public List<EventStripe> getEventStripes(ZoomParams params) {
|
||||
try {
|
||||
return eventStripeCache.get(params);
|
||||
} catch (ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to load Event Stripes from cache for " + params.toString(), ex); //NON-NLS
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public Map<EventType, Long> countEvents(ZoomParams params) {
|
||||
return eventCountsCache.getUnchecked(params);
|
||||
}
|
||||
|
||||
synchronized public int countAllEvents() {
|
||||
return eventDB.countAllEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @param file The AbstractFile to get derived event IDs
|
||||
* for.
|
||||
* @param includeDerivedArtifacts If true, also get event IDs for events
|
||||
* derived from artifacts derived form this
|
||||
* file. If false, only gets events derived
|
||||
* directly from this file (file system
|
||||
* timestamps).
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given file.
|
||||
*/
|
||||
public List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) {
|
||||
return eventDB.getEventIDsForFile(file, includeDerivedArtifacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to get derived event IDs for.
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given artifact.
|
||||
*/
|
||||
public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
|
||||
return eventDB.getEventIDsForArtifact(artifact);
|
||||
}
|
||||
|
||||
private void invalidateCaches() {
|
||||
minCache.invalidateAll();
|
||||
maxCache.invalidateAll();
|
||||
eventCountsCache.invalidateAll();
|
||||
eventStripeCache.invalidateAll();
|
||||
idToEventCache.invalidateAll();
|
||||
}
|
||||
|
||||
public List<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getEventIDs(timeRange, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
public List<CombinedEvent> getCombinedEvents(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getCombinedEvents(timeRange, filter);
|
||||
}
|
||||
|
||||
public Interval getSpanningInterval(Collection<Long> eventIDs) {
|
||||
return eventDB.getSpanningInterval(eventIDs);
|
||||
}
|
||||
|
||||
public boolean hasNewColumns() {
|
||||
return eventDB.hasNewColumns();
|
||||
}
|
||||
|
||||
/**
|
||||
* get a count of tagnames applied to the given event ids as a map from
|
||||
* tagname displayname to count of tag applications
|
||||
*
|
||||
* @param eventIDsWithTags the event ids to get the tag counts map for
|
||||
*
|
||||
* @return a map from tagname displayname to count of applications
|
||||
*/
|
||||
public Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) {
|
||||
return eventDB.getTagCountsByTagName(eventIDsWithTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* use the given SleuthkitCase to update the data used to determine the
|
||||
* available filters.
|
||||
*
|
||||
* @param skCase
|
||||
*/
|
||||
synchronized private void populateFilterData(SleuthkitCase skCase) {
|
||||
|
||||
for (Map.Entry<Long, String> hashSet : eventDB.getHashSetNames().entrySet()) {
|
||||
hashSetMap.putIfAbsent(hashSet.getKey(), hashSet.getValue());
|
||||
}
|
||||
//because there is no way to remove a datasource we only add to this map.
|
||||
for (Long id : eventDB.getDataSourceIDs()) {
|
||||
try {
|
||||
datasourcesMap.putIfAbsent(id, skCase.getContentById(id).getDataSource().getName());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get datasource by ID.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
//should this only be tags applied to files or event bearing artifacts?
|
||||
tagNames.setAll(skCase.getTagNamesInUse());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get tag names in use.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public Set<Long> addTag(long objID, Long artifactID, Tag tag, EventDB.EventTransaction trans) {
|
||||
Set<Long> updatedEventIDs = eventDB.addTag(objID, artifactID, tag, trans);
|
||||
if (!updatedEventIDs.isEmpty()) {
|
||||
invalidateCaches(updatedEventIDs);
|
||||
}
|
||||
return updatedEventIDs;
|
||||
}
|
||||
|
||||
synchronized public Set<Long> deleteTag(long objID, Long artifactID, long tagID, boolean tagged) {
|
||||
Set<Long> updatedEventIDs = eventDB.deleteTag(objID, artifactID, tagID, tagged);
|
||||
if (!updatedEventIDs.isEmpty()) {
|
||||
invalidateCaches(updatedEventIDs);
|
||||
}
|
||||
return updatedEventIDs;
|
||||
}
|
||||
|
||||
synchronized private void invalidateCaches(Set<Long> updatedEventIDs) {
|
||||
eventCountsCache.invalidateAll();
|
||||
eventStripeCache.invalidateAll();
|
||||
idToEventCache.invalidateAll(updatedEventIDs);
|
||||
try {
|
||||
tagNames.setAll(autoCase.getSleuthkitCase().getTagNamesInUse());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get tag names in use.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "sync" the given tags filter with the tagnames in use: Disable filters
|
||||
* for tags that are not in use in the case, and add new filters for tags
|
||||
* that don't have them. New filters are selected by default.
|
||||
*
|
||||
* @param tagsFilter the tags filter to modify so it is consistent with the
|
||||
* tags in use in the case
|
||||
*/
|
||||
public void syncTagsFilter(TagsFilter tagsFilter) {
|
||||
for (TagName t : tagNames) {
|
||||
tagsFilter.addSubFilter(new TagNameFilter(t, autoCase));
|
||||
}
|
||||
for (TagNameFilter t : tagsFilter.getSubFilters()) {
|
||||
t.setDisabled(tagNames.contains(t.getTagName()) == false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) {
|
||||
return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* rebuild the entire repo.
|
||||
*
|
||||
* @param onStateChange called when he background task changes state.
|
||||
* Clients can use this to handle failure, or cleanup
|
||||
* operations for example.
|
||||
*
|
||||
* @return the task that will rebuild the repo in a background thread. The
|
||||
* task has already been started.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
public CancellationProgressTask<Void> rebuildRepository(Consumer<Worker.State> onStateChange) {
|
||||
return rebuildRepository(DBPopulationMode.FULL, onStateChange);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* drop and rebuild the tags in the repo.
|
||||
*
|
||||
* @param onStateChange called when he background task changes state.
|
||||
* Clients can use this to handle failure, or cleanup
|
||||
* operations for example.
|
||||
*
|
||||
* @return the task that will rebuild the repo in a background thread. The
|
||||
* task has already been started.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
public CancellationProgressTask<Void> rebuildTags(Consumer<Worker.State> onStateChange) {
|
||||
return rebuildRepository(DBPopulationMode.TAGS_ONLY, onStateChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* rebuild the repo.
|
||||
*
|
||||
* @param mode the rebuild mode to use.
|
||||
* @param onStateChange called when he background task changes state.
|
||||
* Clients can use this to handle failure, or cleanup
|
||||
* operations for example.
|
||||
*
|
||||
* @return the task that will rebuild the repo in a background thread. The
|
||||
* task has already been started.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private CancellationProgressTask<Void> rebuildRepository(final DBPopulationMode mode, Consumer<Worker.State> onStateChange) {
|
||||
logger.log(Level.INFO, "(re)starting {0} db population task", mode); //NON-NLS
|
||||
if (dbWorker != null) {
|
||||
dbWorker.cancel();
|
||||
}
|
||||
dbWorker = new DBPopulationWorker(mode, onStateChange);
|
||||
workerExecutor.execute(dbWorker);
|
||||
return dbWorker;
|
||||
}
|
||||
|
||||
private enum DBPopulationMode {
|
||||
|
||||
FULL,
|
||||
TAGS_ONLY;
|
||||
}
|
||||
|
||||
/**
|
||||
* //TODO: I don't like the coupling to ProgressHandle in this task, but
|
||||
* the alternatives I can think of seem even worse. -jm
|
||||
*/
|
||||
private class DBPopulationWorker extends CancellationProgressTask<Void> {
|
||||
|
||||
private final ReadOnlyBooleanWrapper cancellable = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
private final DBPopulationMode dbPopulationMode;
|
||||
private final SleuthkitCase skCase;
|
||||
private final TagsManager tagsManager;
|
||||
|
||||
private ProgressHandle progressHandle;
|
||||
|
||||
@Override
|
||||
public ReadOnlyBooleanProperty cancellableProperty() {
|
||||
return cancellable.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestCancel() {
|
||||
Platform.runLater(() -> cancellable.set(false));
|
||||
return super.requestCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTitle(String title) {
|
||||
super.updateTitle(title);
|
||||
progressHandle.setDisplayName(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateMessage(String message) {
|
||||
super.updateMessage(message);
|
||||
progressHandle.progress(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProgress(double workDone, double max) {
|
||||
super.updateProgress(workDone, max);
|
||||
if (workDone >= 0) {
|
||||
progressHandle.progress((int) workDone);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProgress(long workDone, long max) {
|
||||
super.updateProgress(workDone, max);
|
||||
super.updateProgress(workDone, max);
|
||||
if (workDone >= 0) {
|
||||
progressHandle.progress((int) workDone);
|
||||
}
|
||||
}
|
||||
|
||||
DBPopulationWorker(DBPopulationMode mode, Consumer<Worker.State> onStateChange) {
|
||||
skCase = autoCase.getSleuthkitCase();
|
||||
tagsManager = autoCase.getServices().getTagsManager();
|
||||
this.dbPopulationMode = mode;
|
||||
this.stateProperty().addListener(stateObservable -> onStateChange.accept(getState()));
|
||||
}
|
||||
|
||||
void restartProgressHandle(String title, String message, Double workDone, double total, Boolean cancellable) {
|
||||
if (progressHandle != null) {
|
||||
progressHandle.finish();
|
||||
}
|
||||
progressHandle = cancellable
|
||||
? ProgressHandle.createHandle(title, this::requestCancel)
|
||||
: ProgressHandle.createHandle(title);
|
||||
|
||||
if (workDone < 0) {
|
||||
progressHandle.start();
|
||||
} else {
|
||||
progressHandle.start((int) total);
|
||||
}
|
||||
updateTitle(title);
|
||||
updateMessage(message);
|
||||
updateProgress(workDone, total);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId
|
||||
@Override
|
||||
@NbBundle.Messages({"progressWindow.msg.refreshingFileTags=Refreshing file tags",
|
||||
"progressWindow.msg.refreshingResultTags=Refreshing result tags",
|
||||
"progressWindow.msg.gatheringData=Gathering event data",
|
||||
"progressWindow.msg.commitingDb=Committing events database"})
|
||||
protected Void call() throws Exception {
|
||||
EventDB.EventTransaction trans = null;
|
||||
|
||||
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
|
||||
restartProgressHandle(Bundle.progressWindow_msg_gatheringData(), "", -1D, 1, true);
|
||||
//reset database //TODO: can we do more incremental updates? -jm
|
||||
eventDB.reInitializeDB();
|
||||
//grab ids of all files
|
||||
List<Long> fileIDs = skCase.findAllFileIdsWhere("name != '.' AND name != '..'" +
|
||||
" AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()); //NON-NLS
|
||||
final int numFiles = fileIDs.size();
|
||||
|
||||
trans = eventDB.beginTransaction();
|
||||
insertMACTimeEvents(numFiles, fileIDs, trans);
|
||||
insertArtifactDerivedEvents(trans);
|
||||
}
|
||||
|
||||
//tags
|
||||
if (dbPopulationMode == DBPopulationMode.TAGS_ONLY) {
|
||||
trans = eventDB.beginTransaction();
|
||||
logger.log(Level.INFO, "dropping old tags"); // NON-NLS
|
||||
eventDB.reInitializeTags();
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "updating content tags"); // NON-NLS
|
||||
List<ContentTag> contentTags = tagsManager.getAllContentTags();
|
||||
int currentWorkTotal = contentTags.size();
|
||||
restartProgressHandle(Bundle.progressWindow_msg_refreshingFileTags(), "", 0D, currentWorkTotal, true);
|
||||
insertContentTags(currentWorkTotal, contentTags, trans);
|
||||
|
||||
logger.log(Level.INFO, "updating artifact tags"); // NON-NLS
|
||||
List<BlackboardArtifactTag> artifactTags = tagsManager.getAllBlackboardArtifactTags();
|
||||
currentWorkTotal = artifactTags.size();
|
||||
restartProgressHandle(Bundle.progressWindow_msg_refreshingResultTags(), "", 0D, currentWorkTotal, true);
|
||||
insertArtifactTags(currentWorkTotal, artifactTags, trans);
|
||||
|
||||
logger.log(Level.INFO, "committing db"); // NON-NLS
|
||||
Platform.runLater(() -> cancellable.set(false));
|
||||
restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false);
|
||||
eventDB.commitTransaction(trans);
|
||||
|
||||
eventDB.analyze();
|
||||
populateFilterData(skCase);
|
||||
invalidateCaches();
|
||||
|
||||
progressHandle.finish();
|
||||
if (isCancelRequested()) {
|
||||
cancel();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void insertArtifactTags(int currentWorkTotal, List<BlackboardArtifactTag> artifactTags, EventDB.EventTransaction trans) {
|
||||
for (int i = 0; i < currentWorkTotal; i++) {
|
||||
if (isCancelRequested()) {
|
||||
break;
|
||||
}
|
||||
updateProgress(i, currentWorkTotal);
|
||||
BlackboardArtifactTag artifactTag = artifactTags.get(i);
|
||||
eventDB.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag, trans);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertContentTags(int currentWorkTotal, List<ContentTag> contentTags, EventDB.EventTransaction trans) {
|
||||
for (int i = 0; i < currentWorkTotal; i++) {
|
||||
if (isCancelRequested()) {
|
||||
break;
|
||||
}
|
||||
updateProgress(i, currentWorkTotal);
|
||||
ContentTag contentTag = contentTags.get(i);
|
||||
eventDB.addTag(contentTag.getContent().getId(), null, contentTag, trans);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertArtifactDerivedEvents(EventDB.EventTransaction trans) {
|
||||
//insert artifact based events
|
||||
//TODO: use (not-yet existing api) to grab all artifacts with timestamps, rather than the hardcoded lists in EventType -jm
|
||||
for (EventType type : RootEventType.allTypes) {
|
||||
if (isCancelRequested()) {
|
||||
break;
|
||||
}
|
||||
//skip file_system events, they are already handled above.
|
||||
if (type instanceof ArtifactEventType) {
|
||||
populateEventType((ArtifactEventType) type, trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages("progressWindow.msg.populateMacEventsFiles=Populating MAC time events for files")
|
||||
private void insertMACTimeEvents(final int numFiles, List<Long> fileIDs, EventDB.EventTransaction trans) {
|
||||
restartProgressHandle(Bundle.progressWindow_msg_populateMacEventsFiles(), "", 0D, numFiles, true);
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
if (isCancelRequested()) {
|
||||
break;
|
||||
}
|
||||
long fID = fileIDs.get(i);
|
||||
try {
|
||||
AbstractFile f = skCase.getAbstractFileById(fID);
|
||||
|
||||
if (isNull(f)) {
|
||||
logger.log(Level.WARNING, "Failed to get data for file : {0}", fID); // NON-NLS
|
||||
} else {
|
||||
insertEventsForFile(f, trans);
|
||||
updateProgress(i, numFiles);
|
||||
updateMessage(f.getName());
|
||||
}
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.SEVERE, "Failed to insert MAC time events for file : " + fID, tskCoreException); // NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insertEventsForFile(AbstractFile f, EventDB.EventTransaction trans) throws TskCoreException {
|
||||
//gather time stamps into map
|
||||
EnumMap<FileSystemTypes, Long> timeMap = new EnumMap<>(FileSystemTypes.class);
|
||||
timeMap.put(FileSystemTypes.FILE_CREATED, f.getCrtime());
|
||||
timeMap.put(FileSystemTypes.FILE_ACCESSED, f.getAtime());
|
||||
timeMap.put(FileSystemTypes.FILE_CHANGED, f.getCtime());
|
||||
timeMap.put(FileSystemTypes.FILE_MODIFIED, f.getMtime());
|
||||
|
||||
/*
|
||||
* if there are no legitimate ( greater than zero ) time stamps (
|
||||
* eg, logical/local files) skip the rest of the event generation:
|
||||
* this should result in dropping logical files, since they do not
|
||||
* have legitimate time stamps.
|
||||
*/
|
||||
if (Collections.max(timeMap.values()) > 0) {
|
||||
final String uniquePath = f.getUniquePath();
|
||||
final String parentPath = f.getParentPath();
|
||||
long datasourceID = f.getDataSource().getId();
|
||||
String datasourceName = StringUtils.substringBeforeLast(uniquePath, parentPath);
|
||||
|
||||
String rootFolder = StringUtils.substringBefore(StringUtils.substringAfter(parentPath, "/"), "/");
|
||||
String shortDesc = datasourceName + "/" + StringUtils.defaultString(rootFolder);
|
||||
shortDesc = shortDesc.endsWith("/") ? shortDesc : shortDesc + "/";
|
||||
String medDesc = datasourceName + parentPath;
|
||||
|
||||
final TskData.FileKnown known = f.getKnown();
|
||||
Set<String> hashSets = f.getHashSetNames();
|
||||
List<ContentTag> tags = tagsManager.getContentTagsByContent(f);
|
||||
|
||||
for (Map.Entry<FileSystemTypes, Long> timeEntry : timeMap.entrySet()) {
|
||||
if (timeEntry.getValue() > 0) {
|
||||
// if the time is legitimate ( greater than zero ) insert it
|
||||
eventDB.insertEvent(timeEntry.getValue(), timeEntry.getKey(),
|
||||
datasourceID, f.getId(), null, uniquePath, medDesc,
|
||||
shortDesc, known, hashSets, tags, trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("msgdlg.problem.text=There was a problem populating the timeline."
|
||||
+ " Not all events may be present or accurate.")
|
||||
protected void done() {
|
||||
super.done();
|
||||
try {
|
||||
get();
|
||||
} catch (CancellationException ex) {
|
||||
logger.log(Level.WARNING, "Timeline database population was cancelled by the user. " //NON-NLS
|
||||
+ " Not all events may be present or accurate."); // NON-NLS
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.WARNING, "Unexpected exception while populating database.", ex); // NON-NLS
|
||||
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.msgdlg_problem_text());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* populate all the events of one type
|
||||
*
|
||||
* @param type the type to populate
|
||||
* @param trans the db transaction to use
|
||||
*/
|
||||
@NbBundle.Messages({"# {0} - event type ", "progressWindow.populatingXevents=Populating {0} events"})
|
||||
private void populateEventType(final ArtifactEventType type, EventDB.EventTransaction trans) {
|
||||
try {
|
||||
//get all the blackboard artifacts corresponding to the given event sub_type
|
||||
final ArrayList<BlackboardArtifact> blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactTypeID());
|
||||
final int numArtifacts = blackboardArtifacts.size();
|
||||
restartProgressHandle(Bundle.progressWindow_populatingXevents(type.getDisplayName()), "", 0D, numArtifacts, true);
|
||||
for (int i = 0; i < numArtifacts; i++) {
|
||||
try {
|
||||
//for each artifact, extract the relevant information for the descriptions
|
||||
insertEventForArtifact(type, blackboardArtifacts.get(i), trans);
|
||||
updateProgress(i, numArtifacts);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "There was a problem inserting event for artifact: " + blackboardArtifacts.get(i).getArtifactID(), ex); // NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "There was a problem getting events with sub type " + type.toString() + ".", ex); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
private void insertEventForArtifact(final ArtifactEventType type, BlackboardArtifact bbart, EventDB.EventTransaction trans) throws TskCoreException {
|
||||
ArtifactEventType.AttributeEventDescription eventDescription = ArtifactEventType.buildEventDescription(type, bbart);
|
||||
|
||||
// if the time is legitimate ( greater than zero ) insert it into the db
|
||||
if (eventDescription != null && eventDescription.getTime() > 0) {
|
||||
long objectID = bbart.getObjectID();
|
||||
Content content = skCase.getContentById(objectID);
|
||||
long datasourceID = content.getDataSource().getId();
|
||||
long artifactID = bbart.getArtifactID();
|
||||
Set<String> hashSets = content.getHashSetNames();
|
||||
List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbart);
|
||||
String fullDescription = eventDescription.getFullDescription();
|
||||
String medDescription = eventDescription.getMedDescription();
|
||||
String shortDescription = eventDescription.getShortDescription();
|
||||
eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, objectID, artifactID, fullDescription, medDescription, shortDescription, null, hashSets, tags, trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 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.db;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.HashSetFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagNameFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.UnionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.FULL;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.MEDIUM;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.TimeUnits;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.DAYS;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.HOURS;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MINUTES;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MONTHS;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.SECONDS;
|
||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.YEARS;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Static helper methods for converting between java "data model" objects and
|
||||
* sqlite queries.
|
||||
*/
|
||||
class SQLHelper {
|
||||
|
||||
static String useHashHitTablesHelper(RootFilter filter) {
|
||||
HashHitsFilter hashHitFilter = filter.getHashHitsFilter();
|
||||
return hashHitFilter.isActive() ? " LEFT JOIN hash_set_hits " : " "; //NON-NLS
|
||||
}
|
||||
|
||||
static String useTagTablesHelper(RootFilter filter) {
|
||||
TagsFilter tagsFilter = filter.getTagsFilter();
|
||||
return tagsFilter.isActive() ? " LEFT JOIN tags " : " "; //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* take the result of a group_concat SQLite operation and split it into a
|
||||
* set of X using the mapper to to convert from string to X
|
||||
*
|
||||
* @param <X> the type of elements to return
|
||||
* @param groupConcat a string containing the group_concat result ( a comma
|
||||
* separated list)
|
||||
* @param mapper a function from String to X
|
||||
*
|
||||
* @return a Set of X, each element mapped from one element of the original
|
||||
* comma delimited string
|
||||
*/
|
||||
static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
|
||||
return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
|
||||
: Stream.of(groupConcat.split(","))
|
||||
.map(mapper::apply)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* get the SQL where clause corresponding to an intersection filter ie
|
||||
* (sub-clause1 and sub-clause2 and ... and sub-clauseN)
|
||||
*
|
||||
* @param filter the filter get the where clause for
|
||||
*
|
||||
* @return an SQL where clause (without the "where") corresponding to the
|
||||
* filter
|
||||
*/
|
||||
private static String getSQLWhere(IntersectionFilter<?> filter) {
|
||||
String join = String.join(" and ", filter.getSubFilters().stream()
|
||||
.filter(Filter::isActive)
|
||||
.map(SQLHelper::getSQLWhere)
|
||||
.collect(Collectors.toList()));
|
||||
return "(" + StringUtils.defaultIfBlank(join, "1") + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* get the SQL where clause corresponding to a union filter ie (sub-clause1
|
||||
* or sub-clause2 or ... or sub-clauseN)
|
||||
*
|
||||
* @param filter the filter get the where clause for
|
||||
*
|
||||
* @return an SQL where clause (without the "where") corresponding to the
|
||||
* filter
|
||||
*/
|
||||
private static String getSQLWhere(UnionFilter<?> filter) {
|
||||
String join = String.join(" or ", filter.getSubFilters().stream()
|
||||
.filter(Filter::isActive)
|
||||
.map(SQLHelper::getSQLWhere)
|
||||
.collect(Collectors.toList()));
|
||||
return "(" + StringUtils.defaultIfBlank(join, "1") + ")";
|
||||
}
|
||||
|
||||
static String getSQLWhere(RootFilter filter) {
|
||||
return getSQLWhere((Filter) filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the SQL where clause corresponding to the given filter
|
||||
*
|
||||
* uses instance of to dispatch to the correct method for each filter type.
|
||||
* NOTE: I don't like this if-else instance of chain, but I can't decide
|
||||
* what to do instead -jm
|
||||
*
|
||||
* @param filter a filter to generate the SQL where clause for
|
||||
*
|
||||
* @return an SQL where clause (without the "where") corresponding to the
|
||||
* filter
|
||||
*/
|
||||
private static String getSQLWhere(Filter filter) {
|
||||
String result = "";
|
||||
if (filter == null) {
|
||||
return "1";
|
||||
} else if (filter instanceof DescriptionFilter) {
|
||||
result = getSQLWhere((DescriptionFilter) filter);
|
||||
} else if (filter instanceof TagsFilter) {
|
||||
result = getSQLWhere((TagsFilter) filter);
|
||||
} else if (filter instanceof HashHitsFilter) {
|
||||
result = getSQLWhere((HashHitsFilter) filter);
|
||||
} else if (filter instanceof DataSourceFilter) {
|
||||
result = getSQLWhere((DataSourceFilter) filter);
|
||||
} else if (filter instanceof DataSourcesFilter) {
|
||||
result = getSQLWhere((DataSourcesFilter) filter);
|
||||
} else if (filter instanceof HideKnownFilter) {
|
||||
result = getSQLWhere((HideKnownFilter) filter);
|
||||
} else if (filter instanceof HashHitsFilter) {
|
||||
result = getSQLWhere((HashHitsFilter) filter);
|
||||
} else if (filter instanceof TextFilter) {
|
||||
result = getSQLWhere((TextFilter) filter);
|
||||
} else if (filter instanceof TypeFilter) {
|
||||
result = getSQLWhere((TypeFilter) filter);
|
||||
} else if (filter instanceof IntersectionFilter) {
|
||||
result = getSQLWhere((IntersectionFilter) filter);
|
||||
} else if (filter instanceof UnionFilter) {
|
||||
result = getSQLWhere((UnionFilter) filter);
|
||||
} else {
|
||||
throw new IllegalArgumentException("getSQLWhere not defined for " + filter.getClass().getCanonicalName());
|
||||
}
|
||||
result = StringUtils.deleteWhitespace(result).equals("(1and1and1)") ? "1" : result; //NON-NLS
|
||||
result = StringUtils.deleteWhitespace(result).equals("()") ? "1" : result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String getSQLWhere(HideKnownFilter filter) {
|
||||
if (filter.isActive()) {
|
||||
return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS
|
||||
} else {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSQLWhere(DescriptionFilter filter) {
|
||||
if (filter.isActive()) {
|
||||
String likeOrNotLike = (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : " NOT") + " LIKE '"; //NON-NLS
|
||||
return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + likeOrNotLike + filter.getDescription() + "' )"; // NON-NLS
|
||||
} else {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSQLWhere(TagsFilter filter) {
|
||||
if (filter.isActive()
|
||||
&& (filter.getSubFilters().isEmpty() == false)) {
|
||||
String tagNameIDs = filter.getSubFilters().stream()
|
||||
.filter((TagNameFilter t) -> t.isSelected() && !t.isDisabled())
|
||||
.map((TagNameFilter t) -> String.valueOf(t.getTagName().getId()))
|
||||
.collect(Collectors.joining(", ", "(", ")"));
|
||||
return "(events.event_id == tags.event_id AND " //NON-NLS
|
||||
+ "tags.tag_name_id IN " + tagNameIDs + ") "; //NON-NLS
|
||||
} else {
|
||||
return "1";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getSQLWhere(HashHitsFilter filter) {
|
||||
if (filter.isActive()
|
||||
&& (filter.getSubFilters().isEmpty() == false)) {
|
||||
String hashSetIDs = filter.getSubFilters().stream()
|
||||
.filter((HashSetFilter t) -> t.isSelected() && !t.isDisabled())
|
||||
.map((HashSetFilter t) -> String.valueOf(t.getHashSetID()))
|
||||
.collect(Collectors.joining(", ", "(", ")"));
|
||||
return "(hash_set_hits.hash_set_id IN " + hashSetIDs + " AND hash_set_hits.event_id == events.event_id)"; //NON-NLS
|
||||
} else {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSQLWhere(DataSourceFilter filter) {
|
||||
if (filter.isActive()) {
|
||||
return "(datasource_id = '" + filter.getDataSourceID() + "')"; //NON-NLS
|
||||
} else {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSQLWhere(DataSourcesFilter filter) {
|
||||
return (filter.isActive()) ? "(datasource_id in (" //NON-NLS
|
||||
+ filter.getSubFilters().stream()
|
||||
.filter(AbstractFilter::isActive)
|
||||
.map((dataSourceFilter) -> String.valueOf(dataSourceFilter.getDataSourceID()))
|
||||
.collect(Collectors.joining(", ")) + "))" : "1";
|
||||
}
|
||||
|
||||
private static String getSQLWhere(TextFilter filter) {
|
||||
if (filter.isActive()) {
|
||||
if (StringUtils.isBlank(filter.getText())) {
|
||||
return "1";
|
||||
}
|
||||
String strippedFilterText = StringUtils.strip(filter.getText());
|
||||
return "((med_description like '%" + strippedFilterText + "%')" //NON-NLS
|
||||
+ " or (full_description like '%" + strippedFilterText + "%')" //NON-NLS
|
||||
+ " or (short_description like '%" + strippedFilterText + "%'))"; //NON-NLS
|
||||
} else {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a sql where clause for the given type filter, while trying to be
|
||||
* as simple as possible to improve performance.
|
||||
*
|
||||
* @param typeFilter
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String getSQLWhere(TypeFilter typeFilter) {
|
||||
if (typeFilter.isSelected() == false) {
|
||||
return "0";
|
||||
} else if (typeFilter.getEventType() instanceof RootEventType) {
|
||||
if (typeFilter.getSubFilters().stream()
|
||||
.allMatch(subFilter -> subFilter.isActive() && subFilter.getSubFilters().stream().allMatch(Filter::isActive))) {
|
||||
return "1"; //then collapse clause to true
|
||||
}
|
||||
}
|
||||
return "(sub_type IN (" + StringUtils.join(getActiveSubTypes(typeFilter), ",") + "))"; //NON-NLS
|
||||
}
|
||||
|
||||
private static List<Integer> getActiveSubTypes(TypeFilter filter) {
|
||||
if (filter.isActive()) {
|
||||
if (filter.getSubFilters().isEmpty()) {
|
||||
return Collections.singletonList(RootEventType.allTypes.indexOf(filter.getEventType()));
|
||||
} else {
|
||||
return filter.getSubFilters().stream().flatMap((Filter t) -> getActiveSubTypes((TypeFilter) t).stream()).collect(Collectors.toList());
|
||||
}
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a sqlite strftime format string that will allow us to group by the
|
||||
* requested period size. That is, with all info more granular than that
|
||||
* requested dropped (replaced with zeros).
|
||||
*
|
||||
* @param timeUnit the {@link TimeUnits} instance describing what
|
||||
* granularity to build a strftime string for
|
||||
*
|
||||
* @return a String formatted according to the sqlite strftime spec
|
||||
*
|
||||
* @see https://www.sqlite.org/lang_datefunc.html
|
||||
*/
|
||||
static String getStrfTimeFormat(@Nonnull TimeUnits timeUnit) {
|
||||
switch (timeUnit) {
|
||||
case YEARS:
|
||||
return "%Y-01-01T00:00:00"; // NON-NLS
|
||||
case MONTHS:
|
||||
return "%Y-%m-01T00:00:00"; // NON-NLS
|
||||
case DAYS:
|
||||
return "%Y-%m-%dT00:00:00"; // NON-NLS
|
||||
case HOURS:
|
||||
return "%Y-%m-%dT%H:00:00"; // NON-NLS
|
||||
case MINUTES:
|
||||
return "%Y-%m-%dT%H:%M:00"; // NON-NLS
|
||||
case SECONDS:
|
||||
default: //seconds - should never happen
|
||||
return "%Y-%m-%dT%H:%M:%S"; // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
static String getDescriptionColumn(DescriptionLoD lod) {
|
||||
switch (lod) {
|
||||
case FULL:
|
||||
return "full_description"; //NON-NLS
|
||||
case MEDIUM:
|
||||
return "med_description"; //NON-NLS
|
||||
case SHORT:
|
||||
default:
|
||||
return "short_description"; //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
private SQLHelper() {
|
||||
}
|
||||
}
|
0
Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java
Normal file → Executable file
1
Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java
Normal file → Executable file
@ -22,6 +22,7 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* A TagsUpdatedEvent for tags that have been added to events.
|
||||
* NOTE: This event is internal to timeline components
|
||||
*/
|
||||
public class TagsAddedEvent extends TagsUpdatedEvent {
|
||||
|
||||
|
1
Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java
Normal file → Executable file
@ -22,6 +22,7 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* A TagsUpdatedEvent for tags that have been removed from events.
|
||||
* NOTE: This event is internal to timeline components
|
||||
*/
|
||||
public class TagsDeletedEvent extends TagsUpdatedEvent {
|
||||
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/events/TagsUpdatedEvent.java
Normal file → Executable file
92
Core/src/org/sleuthkit/autopsy/timeline/events/TimelineEventAddedEvent.java
Executable file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.events;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineEvent;
|
||||
|
||||
/**
|
||||
* An AutopsyEvent broadcast when a TimelineEvent is added to the case.
|
||||
*/
|
||||
public class TimelineEventAddedEvent extends AutopsyEvent {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(TimelineEventAddedEvent.class.getName());
|
||||
|
||||
private transient TimelineEvent addedEvent;
|
||||
|
||||
public TimelineEventAddedEvent(org.sleuthkit.datamodel.TimelineManager.TimelineEventAddedEvent event) {
|
||||
super(Case.Events.TIMELINE_EVENT_ADDED.name(), null, event.getAddedEvent().getEventID());
|
||||
addedEvent = event.getAddedEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TimelineEvent that was added.
|
||||
*
|
||||
* @return The TimelineEvent or null if there is an error retrieving the
|
||||
* TimelineEvent.
|
||||
*/
|
||||
@Override
|
||||
public TimelineEvent getNewValue() {
|
||||
/**
|
||||
* The addedEvent field is set in the constructor, but it is transient
|
||||
* so it will become null when the event is serialized for publication
|
||||
* over a network. Doing a lazy load of the TimelineEvent object
|
||||
* bypasses the issues related to the serialization and de-serialization
|
||||
* of TimelineEvent objects and may also save database round trips from
|
||||
* other nodes since subscribers to this event are often not interested
|
||||
* in the event data.
|
||||
*/
|
||||
if (null != addedEvent) {
|
||||
return addedEvent;
|
||||
}
|
||||
try {
|
||||
Long addedEventID = (Long) super.getNewValue();
|
||||
addedEvent = Case.getCurrentCaseThrows().getSleuthkitCase().getTimelineManager().getEventById(addedEventID);
|
||||
return addedEvent;
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TimelineEvent that was added.
|
||||
*
|
||||
* @return The TimelineEvent or null if there is an error retrieving the
|
||||
* TimelineEvent.
|
||||
*/
|
||||
public TimelineEvent getAddedEvent() {
|
||||
return getNewValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Id of the event that was added.
|
||||
*
|
||||
* @return The Id of the event that was added.
|
||||
*/
|
||||
public long getAddedEventID() {
|
||||
return (long) super.getNewValue();
|
||||
}
|
||||
}
|
0
Core/src/org/sleuthkit/autopsy/timeline/events/ViewInTimelineRequestedEvent.java
Normal file → Executable file
@ -3,9 +3,8 @@ EventNode.getAction.linkedFileMessage=There was a problem getting actions for th
|
||||
# {0} - maximum number of events to display
|
||||
# {1} - the number of events that is too many
|
||||
EventRoodNode.tooManyNode.displayName=Too many events to display. Maximum = {0}. But there are {1} to display.
|
||||
NodeProperty.displayName.baseType=Base Type
|
||||
NodeProperty.displayName.dateTime=Date/Time
|
||||
NodeProperty.displayName.description=Description
|
||||
NodeProperty.displayName.eventType=Event Type
|
||||
NodeProperty.displayName.icon=Icon
|
||||
NodeProperty.displayName.known=Known
|
||||
NodeProperty.displayName.subType=Sub Type
|
||||
|
0
Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties
Normal file → Executable file
141
Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -21,9 +21,12 @@ package org.sleuthkit.autopsy.timeline.explorernodes;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.swing.Action;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
@ -31,54 +34,72 @@ import org.openide.nodes.Children;
|
||||
import org.openide.nodes.PropertySupport;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction;
|
||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.autopsy.timeline.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.EventTypeUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.timeline.EventType;
|
||||
import org.sleuthkit.datamodel.timeline.TimelineEvent;
|
||||
|
||||
/**
|
||||
* * Explorer Node for a SingleEvent.
|
||||
* * Explorer Node for a TimelineEvent.
|
||||
*/
|
||||
public class EventNode extends DisplayableItemNode {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(EventNode.class.getName());
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName());
|
||||
private final TimelineEvent event;
|
||||
|
||||
private final SingleEvent event;
|
||||
|
||||
EventNode(SingleEvent event, AbstractFile file, BlackboardArtifact artifact) {
|
||||
/**
|
||||
* Construct an EvetNode for an event with a Content and a
|
||||
* BlackboardArtifact in its lookup.
|
||||
*
|
||||
* @param event The event this node is for.
|
||||
* @param file The Content the artifact for this event is derived form.
|
||||
* Not Null.
|
||||
* @param artifact The artifact this event is derived from. Not Null.
|
||||
*/
|
||||
EventNode(@Nonnull TimelineEvent event, @Nonnull Content file, @Nonnull BlackboardArtifact artifact) {
|
||||
super(Children.LEAF, Lookups.fixed(event, file, artifact));
|
||||
this.event = event;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
|
||||
EventType evenType = event.getEventType();
|
||||
this.setIconBaseWithExtension(EventTypeUtils.getImagePath(evenType));
|
||||
}
|
||||
|
||||
EventNode(SingleEvent event, AbstractFile file) {
|
||||
/**
|
||||
* Construct an EvetNode for an event with a Content in its lookup.
|
||||
*
|
||||
* @param event The event this node is for.
|
||||
* @param file The Content this event is derived directly from. Not Null.
|
||||
*/
|
||||
EventNode(@Nonnull TimelineEvent event, @Nonnull Content file) {
|
||||
super(Children.LEAF, Lookups.fixed(event, file));
|
||||
this.event = event;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
|
||||
EventType evenType = event.getEventType();
|
||||
this.setIconBaseWithExtension(EventTypeUtils.getImagePath(evenType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"NodeProperty.displayName.icon=Icon",
|
||||
"NodeProperty.displayName.description=Description",
|
||||
"NodeProperty.displayName.baseType=Base Type",
|
||||
"NodeProperty.displayName.subType=Sub Type",
|
||||
"NodeProperty.displayName.eventType=Event Type",
|
||||
"NodeProperty.displayName.known=Known",
|
||||
"NodeProperty.displayName.dateTime=Date/Time"})
|
||||
protected Sheet createSheet() {
|
||||
@ -92,9 +113,7 @@ public class EventNode extends DisplayableItemNode {
|
||||
properties.put(new NodeProperty<>("icon", Bundle.NodeProperty_displayName_icon(), "icon", true)); // NON-NLS //gets overridden with icon
|
||||
properties.put(new TimeProperty("time", Bundle.NodeProperty_displayName_dateTime(), "time ", getDateTimeString()));// NON-NLS
|
||||
properties.put(new NodeProperty<>("description", Bundle.NodeProperty_displayName_description(), "description", event.getFullDescription())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventBaseType", Bundle.NodeProperty_displayName_baseType(), "base type", event.getEventType().getSuperType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventSubType", Bundle.NodeProperty_displayName_subType(), "sub type", event.getEventType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("Known", Bundle.NodeProperty_displayName_known(), "known", event.getKnown().toString())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventType", Bundle.NodeProperty_displayName_eventType(), "event type", event.getEventType().getDisplayName())); // NON-NLS
|
||||
|
||||
return sheet;
|
||||
}
|
||||
@ -116,38 +135,43 @@ public class EventNode extends DisplayableItemNode {
|
||||
"EventNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. "
|
||||
+ " The 'View File in Timeline' action will not be available."})
|
||||
public Action[] getActions(boolean context) {
|
||||
Action[] superActions = super.getActions(context);
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
actionsList.addAll(Arrays.asList(superActions));
|
||||
|
||||
final AbstractFile sourceFile = getLookup().lookup(AbstractFile.class);
|
||||
Collections.addAll(actionsList, super.getActions(context));
|
||||
|
||||
/*
|
||||
* if this event is derived from an artifact, add actions to view the
|
||||
* If this event is derived from an artifact, add actions to view the
|
||||
* source file and a "linked" file, if present.
|
||||
*/
|
||||
final BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class);
|
||||
final Content sourceFile = getLookup().lookup(Content.class);
|
||||
if (artifact != null) {
|
||||
try {
|
||||
//find a linked file such as a downloaded file.
|
||||
AbstractFile linkedfile = findLinked(artifact);
|
||||
if (linkedfile != null) {
|
||||
actionsList.add(ViewFileInTimelineAction.createViewFileAction(linkedfile));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.EventNode_getAction_errorTitle(), Bundle.EventNode_getAction_linkedFileMessage());
|
||||
}
|
||||
|
||||
//if this event has associated content, add the action to view the content in the timeline
|
||||
if (null != sourceFile) {
|
||||
actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(sourceFile));
|
||||
//add the action to view the content in the timeline, only for abstract files ( ie with times)
|
||||
if (sourceFile instanceof AbstractFile) {
|
||||
actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction((AbstractFile) sourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
//get default actions for the source file
|
||||
final List<Action> factoryActions = DataModelActionsFactory.getActions(sourceFile, artifact != null);
|
||||
|
||||
List<Action> factoryActions = DataModelActionsFactory.getActions(sourceFile, artifact != null);
|
||||
actionsList.addAll(factoryActions);
|
||||
if (factoryActions.isEmpty()) { // if there were no factory supplied actions, at least add the tagging actions.
|
||||
actionsList.add(AddBlackboardArtifactTagAction.getInstance());
|
||||
if (isExactlyOneArtifactSelected()) {
|
||||
actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance());
|
||||
}
|
||||
actionsList.addAll(ContextMenuExtensionPoint.getActions());
|
||||
}
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
}
|
||||
|
||||
@ -183,11 +207,11 @@ public class EventNode extends DisplayableItemNode {
|
||||
super(name, String.class, displayName, shortDescription);
|
||||
setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS
|
||||
this.value = value;
|
||||
TimeLineController.getTimeZone().addListener(timeZone -> {
|
||||
TimeLineController.timeZoneProperty().addListener(timeZone -> {
|
||||
try {
|
||||
setValue(getDateTimeString());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
@ -199,10 +223,10 @@ public class EventNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
public void setValue(String newValue) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
String oldValue = getValue();
|
||||
value = t;
|
||||
firePropertyChange("time", oldValue, t); // NON-NLS
|
||||
value = newValue;
|
||||
firePropertyChange("time", oldValue, newValue); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,18 +237,19 @@ public class EventNode extends DisplayableItemNode {
|
||||
* @param eventID The ID of the event this node is for.
|
||||
* @param eventsModel The model that provides access to the events DB.
|
||||
*
|
||||
* @return An EventNode with the file (and artifact) backing this event in
|
||||
* its lookup.
|
||||
* @return An EventNode with the content (and possible artifact) backing
|
||||
* this event in its lookup.
|
||||
*/
|
||||
public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException, NoCurrentCaseException {
|
||||
public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException {
|
||||
|
||||
SleuthkitCase sleuthkitCase = eventsModel.getSleuthkitCase();
|
||||
|
||||
/*
|
||||
* Look up the event by id and creata an EventNode with the appropriate
|
||||
* data in the lookup.
|
||||
*/
|
||||
final SingleEvent eventById = eventsModel.getEventById(eventID);
|
||||
|
||||
SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID());
|
||||
final TimelineEvent eventById = eventsModel.getEventById(eventID);
|
||||
Content file = sleuthkitCase.getContentById(eventById.getFileObjID());
|
||||
|
||||
if (eventById.getArtifactID().isPresent()) {
|
||||
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get());
|
||||
@ -234,29 +259,9 @@ public class EventNode extends DisplayableItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this code started as a cut and past of
|
||||
* DataResultFilterNode.GetPopupActionsDisplayableItemNodeVisitor.findLinked(BlackboardArtifactNode
|
||||
* ba)
|
||||
*
|
||||
* It is now in DisplayableItemNode too, but is not accesible across
|
||||
* packages
|
||||
*
|
||||
* @param artifact
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException {
|
||||
|
||||
BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
|
||||
if (pathIDAttribute != null) {
|
||||
long contentID = pathIDAttribute.getValueLong();
|
||||
if (contentID != -1) {
|
||||
return artifact.getSleuthkitCase().getAbstractFileById(contentID);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
private static boolean isExactlyOneArtifactSelected() {
|
||||
final Collection<BlackboardArtifact> selectedArtifactsList
|
||||
= new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
|
||||
return selectedArtifactsList.size() == 1;
|
||||
}
|
||||
}
|
||||
|
33
Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java
Normal file → Executable file
@ -29,11 +29,10 @@ import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.FilteredEventsModel;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -50,8 +49,8 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
*/
|
||||
public static final int MAX_EVENTS_TO_DISPLAY = 5000;
|
||||
|
||||
public EventRootNode(Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
|
||||
super(Children.create(new EventNodeChildFactory(fileIds, filteredEvents), true), Lookups.singleton(fileIds));
|
||||
public EventRootNode(Collection<Long> eventIds, FilteredEventsModel filteredEvents) {
|
||||
super(Children.create(new EventNodeChildFactory(eventIds, filteredEvents), true), Lookups.singleton(eventIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,11 +84,10 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
* filteredEvents is used to lookup the events from their IDs
|
||||
*/
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
private Map<Long, Node > nodesMap = new HashMap<>();
|
||||
|
||||
private final Map<Long, Node> nodesMap = new HashMap<>();
|
||||
|
||||
EventNodeChildFactory(Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
|
||||
this.eventIDs = fileIds;
|
||||
EventNodeChildFactory(Collection<Long> eventIds, FilteredEventsModel filteredEvents) {
|
||||
this.eventIDs = eventIds;
|
||||
this.filteredEvents = filteredEvents;
|
||||
}
|
||||
|
||||
@ -100,16 +98,12 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
* indicate this.
|
||||
*/
|
||||
if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) {
|
||||
for (Long eventId: eventIDs){
|
||||
if (!nodesMap.containsKey(eventId)) {
|
||||
nodesMap.put(eventId, createNode(eventId));
|
||||
}
|
||||
for (Long eventId : eventIDs) {
|
||||
nodesMap.computeIfAbsent(eventId, this::createNode);
|
||||
toPopulate.add(eventId);
|
||||
}
|
||||
} else {
|
||||
if (!nodesMap.containsKey(-1L)) {
|
||||
nodesMap.put(-1L, createNode(-1L));
|
||||
}
|
||||
nodesMap.computeIfAbsent(-1L, this::createNode);
|
||||
toPopulate.add(-1L);
|
||||
}
|
||||
return true;
|
||||
@ -119,9 +113,8 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
protected Node createNodeForKey(Long eventID) {
|
||||
return nodesMap.get(eventID);
|
||||
}
|
||||
|
||||
|
||||
private Node createNode(Long eventID) {
|
||||
|
||||
if (eventID < 0) {
|
||||
/*
|
||||
* If the eventId is a the special value ( -1 ), return a node
|
||||
@ -131,17 +124,13 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
} else {
|
||||
try {
|
||||
return EventNode.createEventNode(eventID, filteredEvents);
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
return null;
|
||||
} catch (TskCoreException ex) {
|
||||
/*
|
||||
* Just log it: There might be lots of these errors, and we
|
||||
* don't want to flood the user with notifications. It will
|
||||
* be obvious the UI is broken anyways
|
||||
*/
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
LOGGER.log(Level.SEVERE, "Error creating explorer node for event id " + eventID + ".", ex); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 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.filters;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link Filter}. Implements active property.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFilter implements Filter {
|
||||
|
||||
private final SimpleBooleanProperty selected = new SimpleBooleanProperty(true);
|
||||
private final SimpleBooleanProperty disabled = new SimpleBooleanProperty(false);
|
||||
private final BooleanBinding activeProperty = Bindings.and(selected, disabled.not());
|
||||
|
||||
@Override
|
||||
public SimpleBooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableBooleanValue disabledProperty() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(Boolean act) {
|
||||
selected.set(act);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return selected.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisabled(Boolean act) {
|
||||
disabled.set(act);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled() {
|
||||
return disabledProperty().get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return activeProperty().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanBinding activeProperty() {
|
||||
return activeProperty;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
DataSourcesFilter.displayName.text=Data Source
|
||||
DescriptionFilter.mode.exclude=Exclude
|
||||
DescriptionFilter.mode.include=Include
|
||||
hashHitsFilter.displayName.text=Hash Sets
|
||||
hideKnownFilter.displayName.text=Hide Known Files
|
||||
# {0} - sub filter displaynames
|
||||
IntersectionFilter.displayName.text=Intersection{0}
|
||||
tagsFilter.displayName.text=Tags
|
||||
TextFilter.displayName.text=Text Filter
|
||||
TypeFilter.displayName.text=Event Type
|
@ -1,9 +0,0 @@
|
||||
hideKnownFilter.displayName.text=\u65E2\u77E5\u30D5\u30A1\u30A4\u30EB\u3092\u96A0\u3059
|
||||
TextFilter.displayName.text=\u30C6\u30AD\u30B9\u30C8\u30D5\u30A3\u30EB\u30BF\u30FC
|
||||
TypeFilter.displayName.text=\u30A4\u30D9\u30F3\u30C8\u30BF\u30A4\u30D7\u30D5\u30A3\u30EB\u30BF\u30FC
|
||||
IntersectionFilter.displayName.text=\u30A4\u30F3\u30BF\u30FC\u30BB\u30AF\u30B7\u30E7\u30F3{0}
|
||||
DataSourcesFilter.displayName.text=\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9
|
||||
DescriptionFilter.mode.exclude=\u9664\u5916\u3059\u308B
|
||||
DescriptionFilter.mode.include=\u542B\u3080
|
||||
hashHitsFilter.displayName.text=\u30CF\u30C3\u30B7\u30E5\u30BB\u30C3\u30C8\u306E\u30D2\u30C3\u30C8\u306E\u307F
|
||||
tagsFilter.displayName.text=\u30BF\u30B0\u3055\u308C\u305F\u30A4\u30D9\u30F3\u30C8\u306E\u307F
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-16 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.filters;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* A Filter with a collection of {@link Filter} sub-filters. If this filter is
|
||||
* not active than none of its sub-filters are applied either. Concrete
|
||||
* implementations can decide how to combine the sub-filters.
|
||||
*
|
||||
* a {@link CompoundFilter} uses listeners to enforce the following
|
||||
* relationships between it and its sub-filters: if all of a compound filter's
|
||||
* sub-filters become un-selected, un-select the compound filter.
|
||||
*/
|
||||
public abstract class CompoundFilter<SubFilterType extends Filter> extends AbstractFilter {
|
||||
|
||||
/**
|
||||
* the list of sub-filters that make up this filter
|
||||
*/
|
||||
private final ObservableList<SubFilterType> subFilters = FXCollections.observableArrayList();
|
||||
|
||||
public final ObservableList<SubFilterType> getSubFilters() {
|
||||
return subFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* construct a compound filter from a list of other filters to combine.
|
||||
*
|
||||
* @param subFilters
|
||||
*/
|
||||
public CompoundFilter(List<SubFilterType> subFilters) {
|
||||
super();
|
||||
|
||||
//listen to changes in list of subfilters
|
||||
this.subFilters.addListener((ListChangeListener.Change<? extends SubFilterType> change) -> {
|
||||
while (change.next()) {
|
||||
//add a listener to the selected property of each added subfilter
|
||||
change.getAddedSubList().forEach(addedSubFilter -> {
|
||||
//if a subfilter's selected property changes...
|
||||
addedSubFilter.selectedProperty().addListener(selectedProperty -> {
|
||||
//set this compound filter selected af any of the subfilters are selected.
|
||||
setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.subFilters.setAll(subFilters);
|
||||
}
|
||||
|
||||
static <SubFilterType extends Filter> boolean areSubFiltersEqual(final CompoundFilter<SubFilterType> oneFilter, final CompoundFilter<SubFilterType> otherFilter) {
|
||||
if (oneFilter.getSubFilters().size() != otherFilter.getSubFilters().size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < oneFilter.getSubFilters().size(); i++) {
|
||||
final SubFilterType subFilter = oneFilter.getSubFilters().get(i);
|
||||
final SubFilterType otherSubFilter = otherFilter.getSubFilters().get(i);
|
||||
if (subFilter.equals(otherSubFilter) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 61 * hash + Objects.hashCode(this.subFilters);
|
||||
return hash;
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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.filters;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Filter for an individual datasource
|
||||
*/
|
||||
public class DataSourceFilter extends AbstractFilter {
|
||||
|
||||
private final String dataSourceName;
|
||||
private final long dataSourceID;
|
||||
|
||||
public long getDataSourceID() {
|
||||
return dataSourceID;
|
||||
}
|
||||
|
||||
public String getDataSourceName() {
|
||||
return dataSourceName;
|
||||
}
|
||||
|
||||
public DataSourceFilter(String dataSourceName, long dataSourceID) {
|
||||
this.dataSourceName = dataSourceName;
|
||||
this.dataSourceID = dataSourceID;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public DataSourceFilter copyOf() {
|
||||
DataSourceFilter filterCopy = new DataSourceFilter(getDataSourceName(), getDataSourceID());
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return getDataSourceName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
hash = 97 * hash + Objects.hashCode(this.dataSourceName);
|
||||
hash = 97 * hash + (int) (this.dataSourceID ^ (this.dataSourceID >>> 32));
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DataSourceFilter other = (DataSourceFilter) obj;
|
||||
if (!Objects.equals(this.dataSourceName, other.dataSourceName)) {
|
||||
return false;
|
||||
}
|
||||
if (this.dataSourceID != other.dataSourceID) {
|
||||
return false;
|
||||
}
|
||||
return isSelected() == other.isSelected();
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-16 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.filters;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* union of {@link DataSourceFilter}s
|
||||
*/
|
||||
public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
|
||||
|
||||
//keep references to the overridden properties so they don't get GC'd
|
||||
private final BooleanBinding activePropertyOverride;
|
||||
private final BooleanBinding disabledPropertyOverride;
|
||||
|
||||
public DataSourcesFilter() {
|
||||
disabledPropertyOverride = Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1));
|
||||
activePropertyOverride = super.activeProperty().and(Bindings.not(disabledPropertyOverride));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourcesFilter copyOf() {
|
||||
final DataSourcesFilter filterCopy = new DataSourcesFilter();
|
||||
//add a copy of each subfilter
|
||||
getSubFilters().forEach(dataSourceFilter -> filterCopy.addSubFilter(dataSourceFilter.copyOf()));
|
||||
//these need to happen after the listeners fired by adding the subfilters
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("DataSourcesFilter.displayName.text=Data Source")
|
||||
public String getDisplayName() {
|
||||
return Bundle.DataSourcesFilter_displayName_text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DataSourcesFilter other = (DataSourcesFilter) obj;
|
||||
|
||||
if (isActive() != other.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areSubFiltersEqual(this, other);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableBooleanValue disabledProperty() {
|
||||
return disabledPropertyOverride;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanBinding activeProperty() {
|
||||
return activePropertyOverride;
|
||||
}
|
||||
|
||||
@Override
|
||||
Predicate<DataSourceFilter> getDuplicatePredicate(DataSourceFilter subfilter) {
|
||||
return dataSourcefilter -> dataSourcefilter.getDataSourceID() == subfilter.getDataSourceID();
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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.filters;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
|
||||
public class DescriptionFilter extends AbstractFilter {
|
||||
|
||||
private final DescriptionLoD descriptionLoD;
|
||||
private final String description;
|
||||
private final FilterMode filterMode;
|
||||
|
||||
public FilterMode getFilterMode() {
|
||||
return filterMode;
|
||||
}
|
||||
|
||||
public DescriptionFilter(DescriptionLoD descriptionLoD, String description, FilterMode filterMode) {
|
||||
this.descriptionLoD = descriptionLoD;
|
||||
this.description = description;
|
||||
this.filterMode = filterMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DescriptionFilter copyOf() {
|
||||
DescriptionFilter filterCopy = new DescriptionFilter(getDescriptionLoD(), getDescription(), getFilterMode());
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return getDescriptionLoD().getDisplayName() + ": " + getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the descriptionLoD
|
||||
*/
|
||||
public DescriptionLoD getDescriptionLoD() {
|
||||
return descriptionLoD;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"DescriptionFilter.mode.exclude=Exclude",
|
||||
"DescriptionFilter.mode.include=Include"})
|
||||
public enum FilterMode {
|
||||
|
||||
EXCLUDE(Bundle.DescriptionFilter_mode_exclude()),
|
||||
INCLUDE(Bundle.DescriptionFilter_mode_include());
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private FilterMode(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
private String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 79 * hash + Objects.hashCode(this.descriptionLoD);
|
||||
hash = 79 * hash + Objects.hashCode(this.description);
|
||||
hash = 79 * hash + Objects.hashCode(this.filterMode);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DescriptionFilter other = (DescriptionFilter) obj;
|
||||
if (this.descriptionLoD != other.descriptionLoD) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.description, other.description)) {
|
||||
return false;
|
||||
}
|
||||
if (this.filterMode != other.filterMode) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 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.filters;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* Interface for Filters. Filters are given to the EventDB who interpretes them
|
||||
* a appropriately for all db queries. Since the filters are primarily
|
||||
* configured in the UI, this interface provides selected, disabled and active
|
||||
* (selected and not disabled) properties.
|
||||
*/
|
||||
public interface Filter {
|
||||
|
||||
/**
|
||||
* get a filter that is the intersection of the given filters
|
||||
*
|
||||
* @param filters a set of filters to intersect
|
||||
*
|
||||
* @return a filter that is the intersection of the given filters
|
||||
*/
|
||||
public static IntersectionFilter<Filter> intersect(ObservableList<Filter> filters) {
|
||||
return new IntersectionFilter<>(filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a filter that is the intersection of the given filters
|
||||
*
|
||||
* @param filters a set of filters to intersect
|
||||
*
|
||||
* @return a filter that is the intersection of the given filters
|
||||
*/
|
||||
public static IntersectionFilter<Filter> intersect(Filter[] filters) {
|
||||
return intersect(FXCollections.observableArrayList(filters));
|
||||
}
|
||||
|
||||
/**
|
||||
* since filters have mutable state (selected/disabled/active) and are
|
||||
* observed in various places, we need a mechanism to copy the current state
|
||||
* to keep in the history.
|
||||
*
|
||||
* Concrete sub classes should implement this in a way that preserves the
|
||||
* state and any sub-filters.
|
||||
*
|
||||
* @return a copy of this filter.
|
||||
*/
|
||||
Filter copyOf();
|
||||
|
||||
/**
|
||||
* get the display name of this filter
|
||||
*
|
||||
* @return a name for this filter to show in the UI
|
||||
*/
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
* is this filter selected
|
||||
*
|
||||
* @return true if this filter is selected
|
||||
*/
|
||||
boolean isSelected();
|
||||
|
||||
/**
|
||||
* set this filter selected
|
||||
*
|
||||
* @param selected true to selecte, false to un-select
|
||||
*/
|
||||
void setSelected(Boolean selected);
|
||||
|
||||
/**
|
||||
* observable selected property
|
||||
*
|
||||
* @return the observable selected property for this filter
|
||||
*/
|
||||
SimpleBooleanProperty selectedProperty();
|
||||
|
||||
/**
|
||||
* set the filter disabled
|
||||
*/
|
||||
void setDisabled(Boolean act);
|
||||
|
||||
/**
|
||||
* observable disabled property
|
||||
*
|
||||
* @return the observable disabled property for this filter
|
||||
*/
|
||||
ObservableBooleanValue disabledProperty();
|
||||
|
||||
/**
|
||||
* is this filter disabled
|
||||
*
|
||||
* @return true if this filter is disabled
|
||||
*/
|
||||
boolean isDisabled();
|
||||
|
||||
/**
|
||||
* is this filter active (selected and not disabled)
|
||||
*
|
||||
* @return true if this filter is active
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* observable active property
|
||||
*
|
||||
* @return the observable active property for this filter
|
||||
*/
|
||||
BooleanBinding activeProperty();
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-16 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.filters;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class HashHitsFilter extends UnionFilter<HashSetFilter> {
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("hashHitsFilter.displayName.text=Hash Sets")
|
||||
public String getDisplayName() {
|
||||
return Bundle.hashHitsFilter_displayName_text();
|
||||
}
|
||||
|
||||
public HashHitsFilter() {
|
||||
setSelected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashHitsFilter copyOf() {
|
||||
HashHitsFilter filterCopy = new HashHitsFilter();
|
||||
//add a copy of each subfilter
|
||||
this.getSubFilters().forEach(hashSetFilter -> filterCopy.addSubFilter(hashSetFilter.copyOf()));
|
||||
//these need to happen after the listeners fired by adding the subfilters
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final HashHitsFilter other = (HashHitsFilter) obj;
|
||||
|
||||
if (isActive() != other.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areSubFiltersEqual(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableBooleanValue disabledProperty() {
|
||||
return Bindings.or(super.disabledProperty(), Bindings.isEmpty(getSubFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
Predicate<HashSetFilter> getDuplicatePredicate(HashSetFilter subfilter) {
|
||||
return hashSetFilter -> subfilter.getHashSetID() == hashSetFilter.getHashSetID();
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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.filters;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Filter for an individual hash set
|
||||
*/
|
||||
public class HashSetFilter extends AbstractFilter {
|
||||
|
||||
private final String hashSetName;
|
||||
private final long hashSetID;
|
||||
|
||||
public long getHashSetID() {
|
||||
return hashSetID;
|
||||
}
|
||||
|
||||
public String getHashSetName() {
|
||||
return hashSetName;
|
||||
}
|
||||
|
||||
public HashSetFilter(String hashSetName, long hashSetID) {
|
||||
this.hashSetName = hashSetName;
|
||||
this.hashSetID = hashSetID;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public HashSetFilter copyOf() {
|
||||
HashSetFilter filterCopy = new HashSetFilter(getHashSetName(), getHashSetID());
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return hashSetName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 37 * hash + Objects.hashCode(this.hashSetName);
|
||||
hash = 37 * hash + (int) (this.hashSetID ^ (this.hashSetID >>> 32));
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final HashSetFilter other = (HashSetFilter) obj;
|
||||
if (!Objects.equals(this.hashSetName, other.hashSetName)) {
|
||||
return false;
|
||||
}
|
||||
if (this.hashSetID != other.hashSetID) {
|
||||
return false;
|
||||
}
|
||||
return isSelected() == other.isSelected();
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 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.filters;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Filter to hide known files
|
||||
*/
|
||||
public class HideKnownFilter extends AbstractFilter {
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("hideKnownFilter.displayName.text=Hide Known Files")
|
||||
public String getDisplayName() {
|
||||
return Bundle.hideKnownFilter_displayName_text();
|
||||
}
|
||||
|
||||
public HideKnownFilter() {
|
||||
super();
|
||||
selectedProperty().set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HideKnownFilter copyOf() {
|
||||
HideKnownFilter hideKnownFilter = new HideKnownFilter();
|
||||
hideKnownFilter.setSelected(isSelected());
|
||||
hideKnownFilter.setDisabled(isDisabled());
|
||||
return hideKnownFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final HideKnownFilter other = (HideKnownFilter) obj;
|
||||
|
||||
return isSelected() == other.isSelected();
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 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.filters;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.collections.FXCollections;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Intersection (And) filter
|
||||
*/
|
||||
public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> {
|
||||
|
||||
public IntersectionFilter(List<S> subFilters) {
|
||||
super(subFilters);
|
||||
}
|
||||
|
||||
public IntersectionFilter() {
|
||||
super(FXCollections.<S>observableArrayList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntersectionFilter<S> copyOf() {
|
||||
@SuppressWarnings("unchecked")
|
||||
IntersectionFilter<S> filter = new IntersectionFilter<>(
|
||||
(List<S>) this.getSubFilters().stream()
|
||||
.map(Filter::copyOf)
|
||||
.collect(Collectors.toList()));
|
||||
filter.setSelected(isSelected());
|
||||
filter.setDisabled(isDisabled());
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({"# {0} - sub filter displaynames",
|
||||
"IntersectionFilter.displayName.text=Intersection{0}"})
|
||||
public String getDisplayName() {
|
||||
String collect = getSubFilters().stream()
|
||||
.map(Filter::getDisplayName)
|
||||
.collect(Collectors.joining(",", "[", "]"));
|
||||
return Bundle.IntersectionFilter_displayName_text(collect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
final IntersectionFilter<S> other = (IntersectionFilter<S>) obj;
|
||||
|
||||
if (isSelected() != other.isSelected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < getSubFilters().size(); i++) {
|
||||
if (getSubFilters().get(i).equals(other.getSubFilters().get(i)) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-16 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.filters;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.collections.FXCollections;
|
||||
|
||||
/**
|
||||
* An implementation of IntersectionFilter designed to be used as the root of a
|
||||
* filter tree. provides named access to specific subfilters.
|
||||
*/
|
||||
public class RootFilter extends IntersectionFilter<Filter> {
|
||||
|
||||
private final HideKnownFilter knownFilter;
|
||||
private final TagsFilter tagsFilter;
|
||||
private final HashHitsFilter hashFilter;
|
||||
private final TextFilter textFilter;
|
||||
private final TypeFilter typeFilter;
|
||||
private final DataSourcesFilter dataSourcesFilter;
|
||||
|
||||
public DataSourcesFilter getDataSourcesFilter() {
|
||||
return dataSourcesFilter;
|
||||
}
|
||||
|
||||
public TagsFilter getTagsFilter() {
|
||||
return tagsFilter;
|
||||
}
|
||||
|
||||
public HashHitsFilter getHashHitsFilter() {
|
||||
return hashFilter;
|
||||
}
|
||||
|
||||
public TypeFilter getTypeFilter() {
|
||||
return typeFilter;
|
||||
}
|
||||
|
||||
public HideKnownFilter getKnownFilter() {
|
||||
return knownFilter;
|
||||
}
|
||||
|
||||
public TextFilter getTextFilter() {
|
||||
return textFilter;
|
||||
}
|
||||
|
||||
public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, Set<Filter> annonymousSubFilters) {
|
||||
super(FXCollections.observableArrayList(
|
||||
textFilter,
|
||||
knownFilter,
|
||||
dataSourceFilter, tagsFilter,
|
||||
hashFilter,
|
||||
typeFilter
|
||||
));
|
||||
this.knownFilter = knownFilter;
|
||||
this.tagsFilter = tagsFilter;
|
||||
this.hashFilter = hashFilter;
|
||||
this.textFilter = textFilter;
|
||||
this.typeFilter = typeFilter;
|
||||
this.dataSourcesFilter = dataSourceFilter;
|
||||
getSubFilters().addAll(annonymousSubFilters);
|
||||
setSelected(Boolean.TRUE);
|
||||
setDisabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootFilter copyOf() {
|
||||
Set<Filter> annonymousSubFilters = getSubFilters().stream()
|
||||
.filter(subFilter ->
|
||||
!(subFilter.equals(knownFilter)
|
||||
|| subFilter.equals(tagsFilter)
|
||||
|| subFilter.equals(hashFilter)
|
||||
|| subFilter.equals(typeFilter)
|
||||
|| subFilter.equals(textFilter)
|
||||
|| subFilter.equals(dataSourcesFilter)))
|
||||
.map(Filter::copyOf)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
RootFilter filter = new RootFilter(
|
||||
knownFilter.copyOf(),
|
||||
tagsFilter.copyOf(),
|
||||
hashFilter.copyOf(),
|
||||
textFilter.copyOf(),
|
||||
typeFilter.copyOf(),
|
||||
dataSourcesFilter.copyOf(),
|
||||
annonymousSubFilters);
|
||||
filter.setSelected(isSelected());
|
||||
filter.setDisabled(isDisabled());
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return areSubFiltersEqual(this, (CompoundFilter<Filter>) obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanBinding activeProperty() {
|
||||
|
||||
return new BooleanBinding() {
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-2018 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.filters;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
* Filter for an individual TagName
|
||||
*/
|
||||
public class TagNameFilter extends AbstractFilter {
|
||||
|
||||
private final TagName tagName;
|
||||
private final Case autoCase;
|
||||
|
||||
public TagNameFilter(TagName tagName, Case autoCase) {
|
||||
this.autoCase = autoCase;
|
||||
this.tagName = tagName;
|
||||
setSelected(Boolean.TRUE);
|
||||
}
|
||||
|
||||
public TagName getTagName() {
|
||||
return tagName;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public TagNameFilter copyOf() {
|
||||
TagNameFilter filterCopy = new TagNameFilter(getTagName(), autoCase);
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return tagName.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 53 * hash + Objects.hashCode(this.tagName);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TagNameFilter other = (TagNameFilter) obj;
|
||||
if (!Objects.equals(this.tagName, other.tagName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isSelected() == other.isSelected();
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-16 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.filters;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
* Filter to show only events tag with the tagNames of the selected subfilters.
|
||||
*/
|
||||
public class TagsFilter extends UnionFilter<TagNameFilter> {
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("tagsFilter.displayName.text=Tags")
|
||||
public String getDisplayName() {
|
||||
return Bundle.tagsFilter_displayName_text();
|
||||
}
|
||||
|
||||
public TagsFilter() {
|
||||
setSelected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagsFilter copyOf() {
|
||||
TagsFilter filterCopy = new TagsFilter();
|
||||
//add a copy of each subfilter
|
||||
getSubFilters().forEach(tagNameFilter -> filterCopy.addSubFilter(tagNameFilter.copyOf()));
|
||||
//these need to happen after the listeners fired by adding the subfilters
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TagsFilter other = (TagsFilter) obj;
|
||||
|
||||
if (isActive() != other.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areSubFiltersEqual(this, other);
|
||||
}
|
||||
|
||||
public void removeFilterForTag(TagName tagName) {
|
||||
getSubFilters().removeIf(subfilter -> subfilter.getTagName().equals(tagName));
|
||||
getSubFilters().sort(Comparator.comparing(TagNameFilter::getDisplayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableBooleanValue disabledProperty() {
|
||||
return Bindings.or(super.disabledProperty(), Bindings.isEmpty(getSubFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
Predicate<TagNameFilter> getDuplicatePredicate(TagNameFilter subfilter) {
|
||||
return tagNameFilter -> subfilter.getTagName().equals(tagNameFilter.getTagName());
|
||||
}
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 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.filters;
|
||||
|
||||
import java.util.Objects;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Filter for text matching
|
||||
*/
|
||||
public class TextFilter extends AbstractFilter {
|
||||
|
||||
public TextFilter() {
|
||||
}
|
||||
|
||||
public TextFilter(String text) {
|
||||
this.text.set(text);
|
||||
}
|
||||
|
||||
private final SimpleStringProperty text = new SimpleStringProperty();
|
||||
|
||||
synchronized public void setText(String text) {
|
||||
this.text.set(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("TextFilter.displayName.text=Text Filter")
|
||||
public String getDisplayName() {
|
||||
return Bundle.TextFilter_displayName_text();
|
||||
}
|
||||
|
||||
synchronized public String getText() {
|
||||
return text.getValue();
|
||||
}
|
||||
|
||||
public Property<String> textProperty() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public TextFilter copyOf() {
|
||||
TextFilter textFilter = new TextFilter(getText());
|
||||
textFilter.setSelected(isSelected());
|
||||
textFilter.setDisabled(isDisabled());
|
||||
return textFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TextFilter other = (TextFilter) obj;
|
||||
|
||||
if (isSelected() != other.isSelected()) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(text.get(), other.text.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
hash = 29 * hash + Objects.hashCode(this.text.get());
|
||||
return hash;
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 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.filters;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
|
||||
/**
|
||||
* Event Type Filter. An instance of TypeFilter is usually a tree that parallels
|
||||
* the event type hierarchy with one filter/node for each event type.
|
||||
*/
|
||||
public class TypeFilter extends UnionFilter<TypeFilter> {
|
||||
|
||||
static private final Comparator<TypeFilter> comparator = Comparator.comparing(TypeFilter::getEventType, EventType.getComparator());
|
||||
|
||||
/**
|
||||
* the event type this filter passes
|
||||
*/
|
||||
private final EventType eventType;
|
||||
|
||||
/**
|
||||
* private constructor that enables non recursive/tree construction of the
|
||||
* filter hierarchy for use in {@link TypeFilter#copyOf()}.
|
||||
*
|
||||
* @param et the event type this filter passes
|
||||
* @param recursive true if subfilters should be added for each subtype.
|
||||
* False if no subfilters should be added.
|
||||
*/
|
||||
private TypeFilter(EventType et, boolean recursive) {
|
||||
super(FXCollections.observableArrayList());
|
||||
this.eventType = et;
|
||||
|
||||
if (recursive) { // add subfilters for each subtype
|
||||
for (EventType subType : et.getSubTypes()) {
|
||||
addSubFilter(new TypeFilter(subType), comparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* public constructor. creates a subfilter for each subtype of the given
|
||||
* event type
|
||||
*
|
||||
* @param et the event type this filter will pass
|
||||
*/
|
||||
public TypeFilter(EventType et) {
|
||||
this(et, true);
|
||||
}
|
||||
|
||||
public EventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("TypeFilter.displayName.text=Event Type")
|
||||
public String getDisplayName() {
|
||||
return (eventType == RootEventType.getInstance())
|
||||
? Bundle.TypeFilter_displayName_text()
|
||||
: eventType.getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a color to use in GUI components representing this filter
|
||||
*/
|
||||
public Color getColor() {
|
||||
return eventType.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an image to use in GUI components representing this filter
|
||||
*/
|
||||
public Image getFXImage() {
|
||||
return eventType.getFXImage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeFilter copyOf() {
|
||||
//make a nonrecursive copy of this filter
|
||||
final TypeFilter filterCopy = new TypeFilter(eventType, false);
|
||||
//add a copy of each subfilter
|
||||
getSubFilters().forEach(typeFilter -> filterCopy.addSubFilter(typeFilter.copyOf(), comparator));
|
||||
//these need to happen after the listeners fired by adding the subfilters
|
||||
filterCopy.setSelected(isSelected());
|
||||
filterCopy.setDisabled(isDisabled());
|
||||
return filterCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TypeFilter other = (TypeFilter) obj;
|
||||
|
||||
if (isActive() != other.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.eventType != other.eventType) {
|
||||
return false;
|
||||
}
|
||||
return areSubFiltersEqual(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 67 * hash + Objects.hashCode(this.eventType);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
Predicate<TypeFilter> getDuplicatePredicate(TypeFilter subfilter) {
|
||||
return t -> subfilter.getEventType().equals(t.eventType);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-16 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.filters;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* Union(or) filter
|
||||
*/
|
||||
abstract public class UnionFilter<SubFilterType extends Filter> extends CompoundFilter<SubFilterType> {
|
||||
|
||||
public UnionFilter(ObservableList<SubFilterType> subFilters) {
|
||||
super(subFilters);
|
||||
}
|
||||
|
||||
public UnionFilter() {
|
||||
super(FXCollections.<SubFilterType>observableArrayList());
|
||||
}
|
||||
|
||||
abstract Predicate<SubFilterType> getDuplicatePredicate(SubFilterType subfilter);
|
||||
|
||||
public void addSubFilter(SubFilterType subfilter) {
|
||||
addSubFilter(subfilter, Comparator.comparing(SubFilterType::getDisplayName));
|
||||
}
|
||||
|
||||
protected void addSubFilter(SubFilterType subfilter, Comparator<SubFilterType> comparator) {
|
||||
Predicate<SubFilterType> duplicatePredicate = getDuplicatePredicate(subfilter);
|
||||
if (getSubFilters().stream().anyMatch(duplicatePredicate) == false) {
|
||||
getSubFilters().add(subfilter);
|
||||
}
|
||||
getSubFilters().sort(comparator);
|
||||
}
|
||||
}
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/action_log.png
Executable file
After Width: | Height: | Size: 674 B |
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/add.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-090.png
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-180.png
Normal file → Executable file
Before Width: | Height: | Size: 1022 B After Width: | Height: | Size: 1022 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-270.png
Normal file → Executable file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png
Normal file → Executable file
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 864 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-in.png
Normal file → Executable file
Before Width: | Height: | Size: 760 B After Width: | Height: | Size: 760 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-out.png
Normal file → Executable file
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 776 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-step-out.png
Normal file → Executable file
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 577 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow-step.png
Normal file → Executable file
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 587 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow.png
Normal file → Executable file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow_in.png
Normal file → Executable file
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/arrow_out.png
Normal file → Executable file
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 594 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/block.png
Normal file → Executable file
Before Width: | Height: | Size: 609 B After Width: | Height: | Size: 609 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/blue-document-attribute-a.png
Normal file → Executable file
Before Width: | Height: | Size: 643 B After Width: | Height: | Size: 643 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/blue-document-attribute-b.png
Normal file → Executable file
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 642 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/blue-document-attribute-c.png
Normal file → Executable file
Before Width: | Height: | Size: 638 B After Width: | Height: | Size: 638 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/blue-document-attribute-m.png
Normal file → Executable file
Before Width: | Height: | Size: 654 B After Width: | Height: | Size: 654 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/blue-document.png
Normal file → Executable file
Before Width: | Height: | Size: 519 B After Width: | Height: | Size: 519 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/bookmark--plus.png
Normal file → Executable file
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 727 B |
0
Core/src/org/sleuthkit/autopsy/timeline/images/bookmarks.png
Normal file → Executable file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |