From 7744cf7fd2a89971d37f53850caffba3259c19d4 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 5 Aug 2015 16:57:04 -0400 Subject: [PATCH] WIP to use sqlite ATTACH DATABASE to implement tag filters --- .../timeline/events/FilteredEventsModel.java | 26 +++++- .../autopsy/timeline/events/db/EventDB.java | 20 ++++- .../timeline/events/db/EventsRepository.java | 32 +++++-- .../autopsy/timeline/events/db/SQLHelper.java | 30 ++++++- .../timeline/filters/CompoundFilter.java | 2 +- .../timeline/filters/DataSourcesFilter.java | 2 +- .../timeline/filters/HashHitsFilter.java | 2 +- .../timeline/filters/HashSetFilter.java | 3 +- .../autopsy/timeline/filters/RootFilter.java | 14 ++- .../timeline/filters/TagNameFilter.java | 79 +++++++++++++++++ .../autopsy/timeline/filters/TagsFilter.java | 87 +++++++++++++++++++ .../autopsy/timeline/filters/TypeFilter.java | 2 +- 12 files changed, 275 insertions(+), 24 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java index 07915a15fa..27ebd7638d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java @@ -27,6 +27,7 @@ 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.DateTimeZone; @@ -48,6 +49,8 @@ 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; @@ -55,6 +58,7 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** @@ -131,7 +135,14 @@ public final class FilteredEventsModel { hashSourceFilter.setSelected(Boolean.TRUE); hashHitsFilter.addHashSetFilter(hashSourceFilter); }); - return new RootFilter(new HideKnownFilter(), hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter); + + TagsFilter tagsFilter = new TagsFilter(); + repo.getTagNames().stream().forEach(t -> { + TagNameFilter tagNameFilter = new TagNameFilter(t); + tagNameFilter.setSelected(Boolean.TRUE); + tagsFilter.addTagFilter(tagNameFilter); + }); + return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter); } public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty currentStateProperty) { @@ -149,6 +160,19 @@ public final class FilteredEventsModel { rootFilter.getHashHitsFilter().addHashSetFilter(hashSetFilter); requestedFilter.set(rootFilter.copyOf()); }); + repo.getTagNames().addListener((ListChangeListener.Change c) -> { + RootFilter rootFilter = filter().get(); + TagsFilter tagsFilter = rootFilter.getTagsFilter(); + while (c.next()) { + c.getRemoved().forEach(tagsFilter::removeFilterForTag); + c.getAddedSubList().forEach((TagName t) -> { + TagNameFilter tagFilter = new TagNameFilter(t); + tagsFilter.addTagFilter(tagFilter); + }); + + } + requestedFilter.set(rootFilter.copyOf()); + }); requestedFilter.set(getDefaultFilter()); requestedZoomParamters.addListener((Observable observable) -> { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java index 7e682e7255..e2c6f18ede 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java @@ -248,7 +248,7 @@ public class EventDB { */ Map countEventsByType(ZoomParams params) { if (params.getTimeRange() != null) { - return countEvents(params.getTimeRange().getStartMillis() / 1000, + return countEventsByType(params.getTimeRange().getStartMillis() / 1000, params.getTimeRange().getEndMillis() / 1000, params.getFilter(), params.getTypeZoomLevel()); } else { @@ -791,6 +791,17 @@ public class EventDB { DBLock.lock(); //this should match Sleuthkit db setupt try (Statement statement = con.createStatement()) { + String toString = Paths.get(dbPath).getParent().resolve("autopsy.db").toString(); + System.out.println(toString); + + ResultSet rs = statement.executeQuery("PRAGMA database_list"); + boolean found = false; + while (rs.next() && !found) { + found |= "autopsy".equalsIgnoreCase(rs.getString("name")); + } + if (!found) { + statement.execute("ATTACH DATABASE 'file:" + toString + "?mode=ro' AS autopsy"); + } //reduce i/o operations, we have no OS crash recovery anyway statement.execute("PRAGMA synchronous = OFF;"); // NON-NLS //we don't use this feature, so turn it off for minimal speed up on queries @@ -846,7 +857,7 @@ public class EventDB { * @return a map organizing the counts in a hierarchy from date > eventtype> * count */ - private Map countEvents(Long startTime, Long endTime, RootFilter filter, EventTypeZoomLevel zoomLevel) { + private Map countEventsByType(Long startTime, Long endTime, RootFilter filter, EventTypeZoomLevel zoomLevel) { if (Objects.equals(startTime, endTime)) { endTime++; } @@ -931,7 +942,7 @@ public class EventDB { DBLock.lock(); String query = "select strftime('" + strfTimeFormat + "',time , 'unixepoch'" + (TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : "") + ") as interval," + " group_concat(events.event_id) as event_ids, Min(time), Max(time), " + descriptionColumn + ", " + useSubTypeHelper(useSubTypes) - + " from events" + useHashHitTablesHelper(filter) + " where " + "time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS + + " from events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter)+ " where " + "time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS + " group by interval, " + useSubTypeHelper(useSubTypes) + " , " + descriptionColumn // NON-NLS + " order by Min(time)"; // NON-NLS // scoop up requested events in groups organized by interval, type, and desription @@ -1022,6 +1033,9 @@ public class EventDB { private String useHashHitTablesHelper(RootFilter filter) { return SQLHelper.hasActiveHashFilter(filter) ? ", hash_set_hits" : ""; } + private String useTagTablesHelper(RootFilter filter) { + return SQLHelper.hasActiveTagFilter(filter) ? ", content_tags, blackboard_artifact_tags " : ""; + } private static String useSubTypeHelper(final boolean useSubTypes) { return useSubTypes ? "sub_type" : "base_type"; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java index ecc202473d..ca8708ac24 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javax.annotation.concurrent.GuardedBy; import javax.swing.JOptionPane; @@ -57,6 +58,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -95,12 +97,17 @@ public class EventsRepository { private final ObservableMap datasourcesMap = FXCollections.observableHashMap(); private final ObservableMap hashSetMap = FXCollections.observableHashMap(); + private final ObservableList tagNames = FXCollections.observableArrayList(); private final Case autoCase; public Case getAutoCase() { return autoCase; } + public ObservableList getTagNames() { + return tagNames; + } + synchronized public ObservableMap getDatasourcesMap() { return datasourcesMap; } @@ -125,7 +132,7 @@ public class EventsRepository { this.autoCase = autoCase; //TODO: we should check that case is open, or get passed a case object/directory -jm this.eventDB = EventDB.getEventDB(autoCase); - populateFilterMaps(autoCase.getSleuthkitCase()); + populateFilterData(autoCase.getSleuthkitCase()); idToEventCache = CacheBuilder.newBuilder() .maximumSize(5000L) .expireAfterAccess(10, TimeUnit.MINUTES) @@ -289,8 +296,8 @@ public class EventsRepository { String rootFolder = StringUtils.substringBetween(parentPath, "/", "/"); String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, ""); String medD = datasourceName + parentPath; - final TskData.FileKnown known = f.getKnown(); - Set hashSets = f.getHashSetNames() ; + final TskData.FileKnown known = f.getKnown(); + Set hashSets = f.getHashSetNames(); boolean tagged = !tagsManager.getContentTagsByContent(f).isEmpty(); //insert it into the db if time is > 0 => time is legitimate (drops logical files) @@ -336,7 +343,7 @@ public class EventsRepository { eventDB.commitTransaction(trans, true); } - populateFilterMaps(skCase); + populateFilterData(skCase); invalidateCaches(); return null; @@ -400,7 +407,7 @@ public class EventsRepository { long datasourceID = skCase.getContentById(bbart.getObjectID()).getDataSource().getId(); AbstractFile f = skCase.getAbstractFileById(bbart.getObjectID()); - Set hashSets = f.getHashSetNames(); + Set hashSets = f.getHashSetNames(); boolean tagged = tagsManager.getBlackboardArtifactTagsByArtifact(bbart).isEmpty() == false; eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, bbart.getObjectID(), bbart.getArtifactID(), eventDescription.getFullDescription(), eventDescription.getMedDescription(), eventDescription.getShortDescription(), null, hashSets, tagged, trans); @@ -425,7 +432,7 @@ public class EventsRepository { * * @param skCase */ - synchronized private void populateFilterMaps(SleuthkitCase skCase) { + synchronized private void populateFilterData(SleuthkitCase skCase) { for (Map.Entry hashSet : eventDB.getHashSetNames().entrySet()) { hashSetMap.putIfAbsent(hashSet.getKey(), hashSet.getValue()); @@ -438,13 +445,24 @@ public class EventsRepository { LOGGER.log(Level.SEVERE, "Failed to get datasource by ID.", ex); } } + + try { + tagNames.setAll(skCase.getTagNamesInUse()); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get tag names in use.", ex); + } } - synchronized public Set markEventsTagged(long objID, Long artifactID, boolean tagged) { + synchronized public Set markEventsTagged(long objID, Long artifactID, boolean tagged) { Set updatedEventIDs = eventDB.markEventsTagged(objID, artifactID, tagged); if (!updatedEventIDs.isEmpty()) { aggregateEventsCache.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); + } } return updatedEventIDs; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java index 1c63f9f2d9..858fd89065 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java @@ -19,6 +19,8 @@ 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; @@ -46,6 +48,11 @@ public class SQLHelper { return hashHitFilter.isSelected() && false == hashHitFilter.isDisabled(); } + static boolean hasActiveTagFilter(RootFilter filter) { + TagsFilter tagsFilter = filter.getTagsFilter(); + return tagsFilter.isSelected() && false == tagsFilter.isDisabled(); + } + private SQLHelper() { } @@ -61,6 +68,8 @@ public class SQLHelper { String result = ""; if (filter == null) { return "1"; + } else if (filter instanceof TagsFilter) { + result = getSQLWhere((TagsFilter) filter); } else if (filter instanceof HashHitsFilter) { result = getSQLWhere((HashHitsFilter) filter); } else if (filter instanceof DataSourceFilter) { @@ -89,7 +98,22 @@ public class SQLHelper { static String getSQLWhere(HideKnownFilter filter) { if (filter.isSelected()) { - return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS + return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS + } else { + return "1"; + } + } + + static String getSQLWhere(TagsFilter filter) { + if (filter.isSelected() + && (false == filter.isDisabled()) + && (filter.getSubFilters().isEmpty() == false)) { + String collect = filter.getSubFilters().stream() + .filter((TagNameFilter t) -> t.isSelected() && !t.isDisabled()) + .map((TagNameFilter t) -> String.valueOf(t.getTagName().getId())) + .collect(Collectors.joining(", ", "(", ")")); + return "((blackboard_artifact_tags.artifact_id == events.artifact_id AND blackboard_artifact_tags.tag_name_id IN " + collect + ") OR ( content_tags.obj_id == events.file_id AND content_tags.tag_name_id IN " + collect + "))"; + } else { return "1"; } @@ -99,10 +123,10 @@ public class SQLHelper { if (filter.isSelected() && (false == filter.isDisabled()) && (filter.getSubFilters().isEmpty() == false)) { - return "(hash_set_hits.hash_set_id in " + filter.getSubFilters().stream() + return "(hash_set_hits.hash_set_id IN " + filter.getSubFilters().stream() .filter((HashSetFilter t) -> t.isSelected() && !t.isDisabled()) .map((HashSetFilter t) -> String.valueOf(t.getHashSetID())) - .collect(Collectors.joining(", ", "(", ")")) + " and hash_set_hits.event_id == events.event_id)"; + .collect(Collectors.joining(", ", "(", ")")) + " AND hash_set_hits.event_id == events.event_id)"; } else { return "1"; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index f4b7a3938e..297b0a3a1b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -78,7 +78,7 @@ public abstract class CompoundFilter extends Abstr } } - static boolean hashEqualSubFilters(final CompoundFilter oneFilter, final CompoundFilter otherFilter) { + static boolean areSubFiltersEqual(final CompoundFilter oneFilter, final CompoundFilter otherFilter) { if (oneFilter.getSubFilters().size() != otherFilter.getSubFilters().size()) { return false; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java index a791edb9ac..3a3b9ba4fd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java @@ -90,7 +90,7 @@ public class DataSourcesFilter extends UnionFilter { return false; } - return hashEqualSubFilters(this, other); + return areSubFiltersEqual(this, other); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java index 5b97f1f71e..399c31d1d3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java @@ -68,7 +68,7 @@ public class HashHitsFilter extends UnionFilter { return false; } - return hashEqualSubFilters(this, other); + return areSubFiltersEqual(this, other); } public void addHashSetFilter(HashSetFilter hashSetFilter) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java index 9edfbc3ee1..f2d5b82a36 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java @@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Objects; /** - * Filter for an individual datasource + * Filter for an individual hash set */ public class HashSetFilter extends AbstractFilter { @@ -84,5 +84,4 @@ public class HashSetFilter extends AbstractFilter { } return isSelected() == other.isSelected(); } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 9cc428bfc9..db82637028 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -27,6 +27,7 @@ import javafx.collections.FXCollections; public class RootFilter extends IntersectionFilter { private final HideKnownFilter knwonFilter; + private final TagsFilter tagsFilter; private final HashHitsFilter hashFilter; private final TextFilter textFilter; private final TypeFilter typeFilter; @@ -36,13 +37,18 @@ public class RootFilter extends IntersectionFilter { return dataSourcesFilter; } + public TagsFilter getTagsFilter() { + return tagsFilter; + } + public HashHitsFilter getHashHitsFilter() { return hashFilter; } - public RootFilter(HideKnownFilter knownFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) { - super(FXCollections.observableArrayList(knownFilter, hashFilter, textFilter, dataSourceFilter, typeFilter)); + public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) { + super(FXCollections.observableArrayList(knownFilter, tagsFilter, hashFilter, textFilter, dataSourceFilter, typeFilter)); this.knwonFilter = knownFilter; + this.tagsFilter = tagsFilter; this.hashFilter = hashFilter; this.textFilter = textFilter; this.typeFilter = typeFilter; @@ -51,7 +57,7 @@ public class RootFilter extends IntersectionFilter { @Override public RootFilter copyOf() { - RootFilter filter = new RootFilter(knwonFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf()); + RootFilter filter = new RootFilter(knwonFilter.copyOf(), tagsFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf()); filter.setSelected(isSelected()); filter.setDisabled(isDisabled()); return filter; @@ -71,6 +77,6 @@ public class RootFilter extends IntersectionFilter { if (getClass() != obj.getClass()) { return false; } - return hashEqualSubFilters(this, (CompoundFilter) obj); + return areSubFiltersEqual(this, (CompoundFilter) obj); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java new file mode 100644 index 0000000000..e9bd25e56d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java @@ -0,0 +1,79 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.timeline.filters; + +import java.util.Objects; +import org.sleuthkit.datamodel.TagName; + +/** + * Filter for an individual TagName + */ +public class TagNameFilter extends AbstractFilter { + + private final TagName tagName; + + public TagNameFilter(TagName tagName) { + this.tagName = tagName; + } + + public TagName getTagName() { + return tagName; + } + + @Override + synchronized public TagNameFilter copyOf() { + TagNameFilter filterCopy = new TagNameFilter(getTagName()); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return tagName.getDisplayName(); + } + + @Override + public String getHTMLReportString() { + return getDisplayName() + getStringCheckBox(); + } + + @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(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java new file mode 100644 index 0000000000..811874c342 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java @@ -0,0 +1,87 @@ +/* + * 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. + */ +package org.sleuthkit.autopsy.timeline.filters; + +import java.util.stream.Collectors; +import javafx.beans.binding.Bindings; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.TagName; + +/** + * + */ +public class TagsFilter extends UnionFilter { + + @Override + @NbBundle.Messages("tagsFilter.displayName.text=Only Events Tagged") + public String getDisplayName() { + return Bundle.tagsFilter_displayName_text(); + } + + public TagsFilter() { + getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1)); + setSelected(false); + } + + @Override + public TagsFilter copyOf() { + TagsFilter filterCopy = new TagsFilter(); + filterCopy.setSelected(isSelected()); + //add a copy of each subfilter + this.getSubFilters().forEach((TagNameFilter t) -> { + filterCopy.addTagFilter(t.copyOf()); + }); + return filterCopy; + } + + @Override + public String getHTMLReportString() { + //move this logic into SaveSnapshot + String string = getDisplayName() + getStringCheckBox(); + if (getSubFilters().isEmpty() == false) { + string = string + " : " + getSubFilters().stream() + .filter(Filter::isSelected) + .map(Filter::getHTMLReportString) + .collect(Collectors.joining("
  • ", "
    • ", "
    ")); // NON-NLS + } + return string; + } + + @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 (isSelected() != other.isSelected()) { + return false; + } + + return areSubFiltersEqual(this, other); + } + + public void addTagFilter(TagNameFilter tagFilter) { + if (getSubFilters().stream().map(TagNameFilter.class::cast) + .map(TagNameFilter::getTagName) + .filter(t -> t == tagFilter.getTagName()) + .findAny().isPresent() == false) { + getSubFilters().add(tagFilter); + } + } + + public void removeFilterForTag(TagName tagName) { + getSubFilters().removeIf((TagNameFilter t) -> t.getTagName().equals(tagName)); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java index 88658e4435..90e9c0b771 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java @@ -133,7 +133,7 @@ public class TypeFilter extends UnionFilter { if (this.eventType != other.eventType) { return false; } - return hashEqualSubFilters(this, other); + return areSubFiltersEqual(this, other); } @Override