diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index f871fff696..2d1d0c0a03 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -604,7 +604,7 @@ public class Case implements SleuthkitCase.ErrorObserver { SleuthkitCase db; String caseDir; if (caseType == CaseType.SINGLE_USER_CASE) { - String dbPath = metadata.getCaseDatabase(); //NON-NLS + String dbPath = metadata.getCaseDatabasePath(); //NON-NLS db = SleuthkitCase.openCase(dbPath); caseDir = new File(dbPath).getParent(); } else { @@ -612,7 +612,7 @@ public class Case implements SleuthkitCase.ErrorObserver { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled")); } try { - db = SleuthkitCase.openCase(metadata.getCaseDatabase(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); + db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory()); } catch (UserPreferencesException ex) { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index bf0862ba02..bd07a3deaf 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -39,6 +39,7 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.coreutils.XMLUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -53,22 +54,23 @@ public final class CaseMetadata { private static final String FILE_EXTENSION = ".aut"; private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)"); private static final String SCHEMA_VERSION_ONE = "1.0"; - private final static String CREATED_BY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS + private final static String AUTOPSY_CREATED_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS + private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS private static final String CURRENT_SCHEMA_VERSION = "2.0"; private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS - private final static String CREATED_BY_BUILD_ELEMENT_NAME = "CreatedByBuild"; //NON-NLS - private final static String SAVED_BY_BUILD_ELEMENT_NAME = "SavedByBuild"; //NON-NLS + private final static String AUTOPSY_CREATED_BY_ELEMENT_NAME = "CreatedByAutopsyVersion"; //NON-NLS + private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS private final static String CASE_DATABASE_ELEMENT_NAME = "Database"; //NON-NLS - private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS + private final static String TEXT_INDEX_ELEMENT = "TextIndex"; //NON-NLS private final Path metadataFilePath; private Case.CaseType caseType; private String caseName; @@ -77,7 +79,7 @@ public final class CaseMetadata { private String caseDatabase; private String textIndexName; private String createdDate; - private String createdByBuild; + private String createdByVersion; /** * Gets the file extension used for case metadata files. @@ -90,7 +92,7 @@ public final class CaseMetadata { /** * Constructs an object that provides access to the case metadata stored in - * a new case metadata file. + * a new case metadata file that is created using the supplied metadata. * * @param caseDirectory The case directory. * @param caseType The type of case. @@ -113,7 +115,7 @@ public final class CaseMetadata { this.examiner = examiner; this.caseDatabase = caseDatabase; this.textIndexName = caseTextIndexName; - createdByBuild = System.getProperty("netbeans.buildnumber"); + createdByVersion = Version.getVersion(); createdDate = CaseMetadata.DATE_FORMAT.format(new Date()); writeToFile(); } @@ -147,7 +149,7 @@ public final class CaseMetadata { * @return The case directory. */ public String getCaseDirectory() { - return this.metadataFilePath.getParent().toString(); + return metadataFilePath.getParent().toString(); } /** @@ -156,7 +158,7 @@ public final class CaseMetadata { * @return The case type. */ public Case.CaseType getCaseType() { - return this.caseType; + return caseType; } /** @@ -175,10 +177,10 @@ public final class CaseMetadata { * @param caseName A case display name. */ void setCaseName(String caseName) throws CaseMetadataException { - String oldCaseName = this.caseName; + String oldCaseName = caseName; this.caseName = caseName; try { - this.writeToFile(); + writeToFile(); } catch (CaseMetadataException ex) { this.caseName = oldCaseName; throw ex; @@ -204,13 +206,32 @@ public final class CaseMetadata { } /** - * Gets a string identifying the case database. + * Gets the name of the case case database. * - * @return For a single-user case, the full path to the case database file. - * For a multi-user case, the case database name. + * @return The case database name. */ - public String getCaseDatabase() { - return caseDatabase; + public String getCaseDatabaseName() { + if (caseType == Case.CaseType.MULTI_USER_CASE) { + return caseDatabase; + } else { + return Paths.get(caseDatabase).getFileName().toString(); + } + } + + /** + * Gets the full path to the case database file if the case is a single-user + * case. + * + * @return The full path to the case database file for a single-user case. + * + * @throws UnsupportedOperationException If called for a multi-user case. + */ + public String getCaseDatabasePath() throws UnsupportedOperationException { + if (caseType == Case.CaseType.SINGLE_USER_CASE) { + return caseDatabase; + } else { + throw new UnsupportedOperationException(); + } } /** @@ -228,7 +249,7 @@ public final class CaseMetadata { * @return The date this case was created as a string */ String getCreatedDate() { - return this.createdDate; + return createdDate; } /** @@ -238,10 +259,10 @@ public final class CaseMetadata { * @param createdDate The date the case was created as a string. */ void setCreatedDate(String createdDate) throws CaseMetadataException { - String oldCreatedDate = this.createdDate; + String oldCreatedDate = createdDate; this.createdDate = createdDate; try { - this.writeToFile(); + writeToFile(); } catch (CaseMetadataException ex) { this.createdDate = oldCreatedDate; throw ex; @@ -249,27 +270,27 @@ public final class CaseMetadata { } /** - * Gets the build that created the case. + * Gets the Autopsy version that created the case. * * @return A build identifier. */ - String getCreatedByBuild() { - return createdByBuild; + String getCreatedByVersion() { + return createdByVersion; } /** - * Gets the build that created the case. Used for preserving the case - * creation build during single-user to multi-user case conversion. + * Sets the Autopsy version that created the case. Used for preserving this + * metadata during single-user to multi-user case conversion. * * @param buildVersion An build version identifier. */ - void setCreatedByBuild(String buildVersion) throws CaseMetadataException { - String oldCreatedByVersion = this.createdByBuild; - this.createdByBuild = buildVersion; + void setCreatedByVersion(String buildVersion) throws CaseMetadataException { + String oldCreatedByVersion = this.createdByVersion; + this.createdByVersion = buildVersion; try { this.writeToFile(); } catch (CaseMetadataException ex) { - this.createdByBuild = oldCreatedByVersion; + this.createdByVersion = oldCreatedByVersion; throw ex; } } @@ -323,22 +344,22 @@ public final class CaseMetadata { Element rootElement = doc.createElement(ROOT_ELEMENT_NAME); doc.appendChild(rootElement); createChildElement(doc, rootElement, SCHEMA_VERSION_ELEMENT_NAME, CURRENT_SCHEMA_VERSION); - createChildElement(doc, rootElement, CREATED_DATE_ELEMENT_NAME, this.getCreatedDate()); + createChildElement(doc, rootElement, CREATED_DATE_ELEMENT_NAME, createdDate); createChildElement(doc, rootElement, MODIFIED_DATE_ELEMENT_NAME, DATE_FORMAT.format(new Date())); - createChildElement(doc, rootElement, CREATED_BY_BUILD_ELEMENT_NAME, this.getCreatedByBuild()); - createChildElement(doc, rootElement, SAVED_BY_BUILD_ELEMENT_NAME, System.getProperty("netbeans.buildnumber")); + createChildElement(doc, rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, createdByVersion); + createChildElement(doc, rootElement, AUTOPSY_SAVED_BY_ELEMENT_NAME, Version.getVersion()); Element caseElement = doc.createElement(CASE_ELEMENT_NAME); rootElement.appendChild(caseElement); /* * Create the children of the case element. */ - createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, this.getCaseName()); - createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, this.getCaseNumber()); - createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, this.getExaminer()); - createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, this.getCaseType().toString()); - createChildElement(doc, caseElement, CASE_DATABASE_ELEMENT_NAME, this.getCaseDatabaseName()); - createChildElement(doc, caseElement, TEXT_INDEX_NAME_ELEMENT, this.getTextIndexName()); + createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName); + createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseNumber); + createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, examiner); + createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString()); + createChildElement(doc, caseElement, CASE_DATABASE_ELEMENT_NAME, caseDatabase); + createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName); } /** @@ -381,9 +402,9 @@ public final class CaseMetadata { String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true); this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true); if (schemaVersion.equals(SCHEMA_VERSION_ONE)) { - this.createdByBuild = getElementTextContent(rootElement, CREATED_BY_VERSION_ELEMENT_NAME, true); + this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_VERSION_ELEMENT_NAME, true); } else { - this.createdByBuild = getElementTextContent(rootElement, CREATED_BY_BUILD_ELEMENT_NAME, true); + this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true); } /* @@ -403,10 +424,11 @@ public final class CaseMetadata { } if (schemaVersion.equals(SCHEMA_VERSION_ONE)) { this.caseDatabase = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true); + this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true); } else { this.caseDatabase = getElementTextContent(caseElement, CASE_DATABASE_ELEMENT_NAME, true); + this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, true); } - this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true); /* * Update the file to the current schema, if necessary. @@ -414,7 +436,7 @@ public final class CaseMetadata { if (!schemaVersion.equals(CURRENT_SCHEMA_VERSION)) { writeToFile(); } - + } catch (ParserConfigurationException | SAXException | IOException ex) { throw new CaseMetadataException(String.format("Error reading from case metadata file %s", metadataFilePath), ex); } @@ -446,7 +468,7 @@ public final class CaseMetadata { /** * Exception thrown by the CaseMetadata class when there is a problem - * accessing the metadata for a case. + * accessing the metadata for a case. */ public final static class CaseMetadataException extends Exception { @@ -461,17 +483,4 @@ public final class CaseMetadata { } } - /** - * Gets a string identifying the case database. - * - * @return For a single-user case, the full path to the case database file. - * For a multi-user case, the case database name. - * - * @deprecated Use getCaseDatabase instead. - */ - @Deprecated - public String getCaseDatabaseName() { - return getCaseDatabase(); - } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java index 2f024e724a..537cd0f3d2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java @@ -88,7 +88,11 @@ class CasePropertiesForm extends javax.swing.JPanel { current = currentCase; CaseMetadata caseMetadata = currentCase.getCaseMetadata(); - tbDbName.setText(caseMetadata.getCaseDatabase()); + if (caseMetadata.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { + tbDbName.setText(caseMetadata.getCaseDatabasePath()); + } else { + tbDbName.setText(caseMetadata.getCaseDatabaseName()); + } Case.CaseType caseType = caseMetadata.getCaseType(); tbDbType.setText(caseType.getLocalizedDisplayName()); if (caseType == Case.CaseType.SINGLE_USER_CASE) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index 458cd642dc..95d94d1f23 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -198,7 +198,7 @@ public class SingleUserCaseConverter { dbName, solrName); // Set created date. This calls writefile, no need to call it again newCaseMetadata.setCreatedDate(oldCaseMetadata.getCreatedDate()); - newCaseMetadata.setCreatedByBuild(oldCaseMetadata.getCreatedByBuild()); + newCaseMetadata.setCreatedByVersion(oldCaseMetadata.getCreatedByVersion()); // At this point the import has been finished successfully so we can delete the original case // (if requested). This *should* be fairly safe - at this point we know there was an autopsy file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index c77ba8ee75..0a88c968c7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -47,6 +47,7 @@ import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; import javafx.concurrent.Task; import javafx.concurrent.Worker; import javax.annotation.concurrent.GuardedBy; @@ -73,6 +74,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.db.EventsRepository; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; @@ -290,6 +292,21 @@ public class TimeLineController { advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval)); } + private final ObservableSet pinnedEvents = FXCollections.observableSet(); + private final ObservableSet pinnedEventsUnmodifiable = FXCollections.unmodifiableObservableSet(pinnedEvents); + + public void pinEvent(TimeLineEvent event) { + pinnedEvents.add(event); + } + + public void unPinEvent(TimeLineEvent event) { + pinnedEvents.removeIf(event::equals); + } + + public ObservableSet getPinnedEvents() { + return pinnedEventsUnmodifiable; + } + /** * rebuild the repo using the given repo builder (expected to be a member * reference to {@link EventsRepository#rebuildRepository(java.util.function.Consumer) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ResetFilters.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ResetFilters.java index 3d38fb0e4e..bb2a5368b0 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ResetFilters.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ResetFilters.java @@ -28,14 +28,18 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; /** * Action that resets the filters to their initial/default state. */ +@NbBundle.Messages({"ResetFilters.text=Reset all filters", + "RestFilters.longText=Reset all filters to their default state."}) public class ResetFilters extends Action { private FilteredEventsModel eventsModel; - @NbBundle.Messages({"ResetFilters.text=Reset all filters", - "RestFilters.longText=Reset all filters to their default state."}) public ResetFilters(final TimeLineController controller) { - super(Bundle.ResetFilters_text()); + this(Bundle.ResetFilters_text(), controller); + } + + public ResetFilters(String text, TimeLineController controller) { + super(text); setLongText(Bundle.RestFilters_longText()); eventsModel = controller.getEventsModel(); disabledProperty().bind(new BooleanBinding() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index ac5a02d007..c8f51247ba 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,12 +33,12 @@ import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** - * Represents a set of other (TimeLineEvent) events clustered together. All the - * sub events should have the same type and matching descriptions at the - * designated 'zoom level', and be 'close together' in time. + * Represents a set of other events clustered together. All the sub events + * should have the same type and matching descriptions at the designated "zoom + * level", and be "close together" in time. */ @Immutable -public class EventCluster implements EventBundle { +public class EventCluster implements MultiEvent { /** * merge two event clusters into one new event cluster. @@ -57,11 +57,16 @@ public class EventCluster implements EventBundle { if (!cluster1.getDescription().equals(cluster2.getDescription())) { throw new IllegalArgumentException("event clusters are not compatible: they have different descriptions"); } - Sets.SetView idsUnion = Sets.union(cluster1.getEventIDs(), cluster2.getEventIDs()); - Sets.SetView hashHitsUnion = Sets.union(cluster1.getEventIDsWithHashHits(), cluster2.getEventIDsWithHashHits()); - Sets.SetView taggedUnion = Sets.union(cluster1.getEventIDsWithTags(), cluster2.getEventIDsWithTags()); + Sets.SetView idsUnion = + Sets.union(cluster1.getEventIDs(), cluster2.getEventIDs()); + Sets.SetView hashHitsUnion = + Sets.union(cluster1.getEventIDsWithHashHits(), cluster2.getEventIDsWithHashHits()); + Sets.SetView taggedUnion = + Sets.union(cluster1.getEventIDsWithTags(), cluster2.getEventIDsWithTags()); - return new EventCluster(IntervalUtils.span(cluster1.span, cluster2.span), cluster1.getEventType(), idsUnion, hashHitsUnion, taggedUnion, cluster1.getDescription(), cluster1.lod); + return new EventCluster(IntervalUtils.span(cluster1.span, cluster2.span), + cluster1.getEventType(), idsUnion, hashHitsUnion, taggedUnion, + cluster1.getDescription(), cluster1.lod); } final private EventStripe parent; @@ -103,7 +108,9 @@ public class EventCluster implements EventBundle { */ private final ImmutableSet hashHits; - private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLoD lod, EventStripe parent) { + private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, + Set hashHits, Set tagged, String description, DescriptionLoD lod, + EventStripe parent) { this.span = spanningInterval; this.type = type; @@ -115,12 +122,13 @@ public class EventCluster implements EventBundle { this.parent = parent; } - public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLoD lod) { + public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, + Set hashHits, Set tagged, String description, DescriptionLoD lod) { this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); } @Override - public Optional getParentBundle() { + public Optional getParent() { return Optional.ofNullable(parent); } @@ -139,19 +147,16 @@ public class EventCluster implements EventBundle { } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSet getEventIDs() { return eventIDs; } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSet getEventIDsWithHashHits() { return hashHits; } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSet getEventIDsWithTags() { return tagged; } @@ -181,15 +186,53 @@ public class EventCluster implements EventBundle { * EventBundle as the parent. */ public EventCluster withParent(EventStripe parent) { - if (Objects.nonNull(this.parent)) { - throw new IllegalStateException("Event Cluster already has a parent!"); - } return new EventCluster(span, type, eventIDs, hashHits, tagged, description, lod, parent); } @Override - public SortedSet< EventCluster> getClusters() { + public SortedSet getClusters() { return ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)).add(this).build(); } + @Override + public String toString() { + return "EventCluster{" + "description=" + description + ", eventIDs=" + eventIDs.size() + '}'; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Objects.hashCode(this.type); + hash = 23 * hash + Objects.hashCode(this.description); + hash = 23 * hash + Objects.hashCode(this.lod); + hash = 23 * hash + Objects.hashCode(this.eventIDs); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EventCluster other = (EventCluster) obj; + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.type, other.type)) { + return false; + } + if (this.lod != other.lod) { + return false; + } + if (!Objects.equals(this.eventIDs, other.eventIDs)) { + return false; + } + return true; + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index ffd8338cf0..93c3799143 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,10 +22,10 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import java.util.Comparator; +import java.util.Objects; import java.util.Optional; import java.util.SortedSet; import javax.annotation.concurrent.Immutable; -import org.python.google.common.base.Objects; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; @@ -34,15 +34,15 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; * description, and zoom levels, but not necessarily close together in time. */ @Immutable -public final class EventStripe implements EventBundle { +public final class EventStripe implements MultiEvent { public static EventStripe merge(EventStripe u, EventStripe v) { Preconditions.checkNotNull(u); Preconditions.checkNotNull(v); - Preconditions.checkArgument(Objects.equal(u.description, v.description)); - Preconditions.checkArgument(Objects.equal(u.lod, v.lod)); - Preconditions.checkArgument(Objects.equal(u.type, v.type)); - Preconditions.checkArgument(Objects.equal(u.parent, v.parent)); + Preconditions.checkArgument(Objects.equals(u.description, v.description)); + Preconditions.checkArgument(Objects.equals(u.lod, v.lod)); + Preconditions.checkArgument(Objects.equals(u.type, v.type)); + Preconditions.checkArgument(Objects.equals(u.parent, v.parent)); return new EventStripe(u, v); } @@ -82,8 +82,10 @@ public final class EventStripe implements EventBundle { private final ImmutableSet hashHits; public EventStripe withParent(EventCluster parent) { - EventStripe eventStripe = new EventStripe(parent, this.type, this.description, this.lod, clusters, eventIDs, tagged, hashHits); - return eventStripe; + if (java.util.Objects.nonNull(this.parent)) { + throw new IllegalStateException("Event Stripe already has a parent!"); + } + return new EventStripe(parent, this.type, this.description, this.lod, clusters, eventIDs, tagged, hashHits); } private EventStripe(EventCluster parent, EventType type, String description, DescriptionLoD lod, SortedSet clusters, ImmutableSet eventIDs, ImmutableSet tagged, ImmutableSet hashHits) { @@ -98,9 +100,10 @@ public final class EventStripe implements EventBundle { this.hashHits = hashHits; } - public EventStripe(EventCluster cluster, EventCluster parent) { + public EventStripe(EventCluster cluster) { + this.clusters = ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)) - .add(cluster).build(); + .add(cluster.withParent(this)).build(); type = cluster.getEventType(); description = cluster.getDescription(); @@ -108,7 +111,7 @@ public final class EventStripe implements EventBundle { eventIDs = cluster.getEventIDs(); tagged = cluster.getEventIDsWithTags(); hashHits = cluster.getEventIDsWithHashHits(); - this.parent = parent; + this.parent = null; } private EventStripe(EventStripe u, EventStripe v) { @@ -132,14 +135,22 @@ public final class EventStripe implements EventBundle { .addAll(u.getEventIDsWithHashHits()) .addAll(v.getEventIDsWithHashHits()) .build(); - parent = u.getParentBundle().orElse(v.getParentBundle().orElse(null)); + parent = u.getParent().orElse(v.getParent().orElse(null)); } @Override - public Optional getParentBundle() { + public Optional getParent() { return Optional.ofNullable(parent); } + public Optional getParentStripe() { + if (getParent().isPresent()) { + return getParent().get().getParent(); + } else { + return Optional.empty(); + } + } + @Override public String getDescription() { return description; @@ -156,19 +167,16 @@ public final class EventStripe implements EventBundle { } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSet getEventIDs() { return eventIDs; } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSet getEventIDsWithHashHits() { return hashHits; } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSet getEventIDsWithTags() { return tagged; } @@ -184,13 +192,53 @@ public final class EventStripe implements EventBundle { } @Override - @SuppressWarnings("ReturnOfCollectionOrArrayField") public ImmutableSortedSet< EventCluster> getClusters() { return clusters; } @Override public String toString() { - return "EventStripe{" + "description=" + description + ", eventIDs=" + eventIDs.size() + '}'; //NON-NLS + return "EventStripe{" + "description=" + description + ", eventIDs=" + (Objects.isNull(eventIDs) ? 0 : eventIDs.size()) + '}'; //NON-NLS + } + + @Override + public int hashCode() { + int hash = 3; + hash = 79 * hash + Objects.hashCode(this.clusters); + hash = 79 * hash + Objects.hashCode(this.type); + hash = 79 * hash + Objects.hashCode(this.description); + hash = 79 * hash + Objects.hashCode(this.lod); + hash = 79 * hash + Objects.hashCode(this.eventIDs); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EventStripe other = (EventStripe) obj; + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.clusters, other.clusters)) { + return false; + } + if (!Objects.equals(this.type, other.type)) { + return false; + } + if (this.lod != other.lod) { + return false; + } + if (!Objects.equals(this.eventIDs, other.eventIDs)) { + return false; + } + return true; } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index bf161241b1..d53316ef4d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -44,7 +44,8 @@ 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.RefreshRequestedEvent; -import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent; +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; @@ -232,11 +233,11 @@ public final class FilteredEventsModel { return repo.getBoundingEventsInterval(zoomParametersProperty().get().getTimeRange(), zoomParametersProperty().get().getFilter()); } - public TimeLineEvent getEventById(Long eventID) { + public SingleEvent getEventById(Long eventID) { return repo.getEventById(eventID); } - public Set getEventsById(Collection eventIDs) { + public Set getEventsById(Collection eventIDs) { return repo.getEventsById(eventIDs); } @@ -351,14 +352,14 @@ public final class FilteredEventsModel { ContentTag contentTag = evt.getAddedTag(); Content content = contentTag.getContent(); Set updatedEventIDs = repo.addTag(content.getId(), null, contentTag, null); - return postTagsUpdated(updatedEventIDs); + return postTagsAdded(updatedEventIDs); } synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) { BlackboardArtifactTag artifactTag = evt.getAddedTag(); BlackboardArtifact artifact = artifactTag.getArtifact(); Set updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag, null); - return postTagsUpdated(updatedEventIDs); + return postTagsAdded(updatedEventIDs); } synchronized public boolean handleContentTagDeleted(ContentTagDeletedEvent evt) { @@ -367,7 +368,7 @@ public final class FilteredEventsModel { Content content = autoCase.getSleuthkitCase().getContentById(deletedTagInfo.getContentID()); boolean tagged = autoCase.getServices().getTagsManager().getContentTagsByContent(content).isEmpty() == false; Set updatedEventIDs = repo.deleteTag(content.getId(), null, deletedTagInfo.getTagID(), tagged); - return postTagsUpdated(updatedEventIDs); + return postTagsDeleted(updatedEventIDs); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "unable to determine tagged status of content.", ex); //NON-NLS } @@ -380,17 +381,25 @@ public final class FilteredEventsModel { BlackboardArtifact artifact = autoCase.getSleuthkitCase().getBlackboardArtifact(deletedTagInfo.getArtifactID()); boolean tagged = autoCase.getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact).isEmpty() == false; Set updatedEventIDs = repo.deleteTag(artifact.getObjectID(), artifact.getArtifactID(), deletedTagInfo.getTagID(), tagged); - return postTagsUpdated(updatedEventIDs); + return postTagsDeleted(updatedEventIDs); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "unable to determine tagged status of artifact.", ex); //NON-NLS } return false; } - private boolean postTagsUpdated(Set updatedEventIDs) { + private boolean postTagsAdded(Set updatedEventIDs) { boolean tagsUpdated = !updatedEventIDs.isEmpty(); if (tagsUpdated) { - eventbus.post(new TagsUpdatedEvent(updatedEventIDs)); + eventbus.post(new TagsAddedEvent(updatedEventIDs)); + } + return tagsUpdated; + } + + private boolean postTagsDeleted(Set updatedEventIDs) { + boolean tagsUpdated = !updatedEventIDs.isEmpty(); + if (tagsUpdated) { + eventbus.post(new TagsDeletedEvent(updatedEventIDs)); } return tagsUpdated; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/MultiEvent.java similarity index 59% rename from Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java rename to Core/src/org/sleuthkit/autopsy/timeline/datamodel/MultiEvent.java index 7361bf5d88..92fde18b58 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/MultiEvent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,37 +19,16 @@ package org.sleuthkit.autopsy.timeline.datamodel; import java.util.Optional; -import java.util.Set; import java.util.SortedSet; -import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * A interface for groups of events that share some attributes in common. */ -public interface EventBundle> { - - String getDescription(); - - DescriptionLoD getDescriptionLoD(); - - Set getEventIDs(); - - Set getEventIDsWithHashHits(); - - Set getEventIDsWithTags(); - - EventType getEventType(); +public interface MultiEvent> extends TimeLineEvent { long getEndMillis(); - long getStartMillis(); - - Optional getParentBundle(); - - default int getCount() { - return getEventIDs().size(); - } + Optional getParent(); SortedSet getClusters(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java new file mode 100644 index 0000000000..94c425cf4c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/SingleEvent.java @@ -0,0 +1,187 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.datamodel; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Set; +import java.util.SortedSet; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.joda.time.Interval; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; +import org.sleuthkit.datamodel.TskData; + +/** + * A single event. + */ +@Immutable +public class SingleEvent implements TimeLineEvent { + + private final long eventID; + private final long fileID; + private final Long artifactID; + private final long dataSourceID; + + private final long time; + private final EventType subType; + private final ImmutableMap descriptions; + + private final TskData.FileKnown known; + private final boolean hashHit; + private final boolean tagged; + + public SingleEvent(long eventID, long dataSourceID, long objID, @Nullable Long artifactID, long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit, boolean tagged) { + this.eventID = eventID; + this.fileID = objID; + this.artifactID = artifactID == 0 ? null : artifactID; + this.time = time; + this.subType = type; + descriptions = ImmutableMap.of(DescriptionLoD.FULL, fullDescription, + DescriptionLoD.MEDIUM, medDescription, + DescriptionLoD.SHORT, shortDescription); + + this.known = known; + this.hashHit = hashHit; + this.tagged = tagged; + this.dataSourceID = dataSourceID; + } + + public boolean isTagged() { + return tagged; + } + + public boolean isHashHit() { + return hashHit; + } + + @Nullable + public Long getArtifactID() { + return artifactID; + } + + public long getEventID() { + return eventID; + } + + public long getFileID() { + return fileID; + } + + /** + * @return the time in seconds from unix epoch + */ + public long getTime() { + return time; + } + + public EventType getEventType() { + return subType; + } + + public String getFullDescription() { + return getDescription(DescriptionLoD.FULL); + } + + public String getMedDescription() { + return getDescription(DescriptionLoD.MEDIUM); + } + + public String getShortDescription() { + return getDescription(DescriptionLoD.SHORT); + } + + public TskData.FileKnown getKnown() { + return known; + } + + public String getDescription(DescriptionLoD lod) { + return descriptions.get(lod); + } + + public long getDataSourceID() { + return dataSourceID; + } + + @Override + public Set getEventIDs() { + return Collections.singleton(eventID); + } + + @Override + public Set getEventIDsWithHashHits() { + return isHashHit() ? Collections.singleton(eventID) : Collections.emptySet(); + } + + @Override + public Set getEventIDsWithTags() { + return isTagged() ? Collections.singleton(eventID) : Collections.emptySet(); + } + + @Override + public long getEndMillis() { + return time * 1000; + } + + @Override + public long getStartMillis() { + return time * 1000; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 13 * hash + (int) (this.eventID ^ (this.eventID >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final SingleEvent other = (SingleEvent) obj; + if (this.eventID != other.eventID) { + return false; + } + return true; + } + + @Override + public SortedSet getClusters() { + EventCluster eventCluster = new EventCluster(new Interval(time * 1000, time * 1000), subType, getEventIDs(), getEventIDsWithHashHits(), getEventIDsWithTags(), getFullDescription(), DescriptionLoD.FULL); + return ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)).add(eventCluster).build(); + } + + @Override + public String getDescription() { + return getFullDescription(); + } + + @Override + public DescriptionLoD getDescriptionLoD() { + return DescriptionLoD.FULL; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java index 2296edfbf8..49aec1f00d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,101 +18,35 @@ */ package org.sleuthkit.autopsy.timeline.datamodel; -import com.google.common.collect.ImmutableMap; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; +import java.util.Set; +import java.util.SortedSet; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; -import org.sleuthkit.datamodel.TskData; /** - * A single event. + * */ -@Immutable -public class TimeLineEvent { +public interface TimeLineEvent { - private final long eventID; - private final long fileID; - private final Long artifactID; - private final long dataSourceID; + public String getDescription(); - private final long time; - private final EventType subType; - private final ImmutableMap descriptions; + public DescriptionLoD getDescriptionLoD(); - private final TskData.FileKnown known; - private final boolean hashHit; - private final boolean tagged; + Set getEventIDs(); - public TimeLineEvent(long eventID, long dataSourceID, long objID, @Nullable Long artifactID, long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit, boolean tagged) { - this.eventID = eventID; - this.fileID = objID; - this.artifactID = artifactID == 0 ? null : artifactID; - this.time = time; - this.subType = type; - descriptions = ImmutableMap.of(DescriptionLoD.FULL, fullDescription, - DescriptionLoD.MEDIUM, medDescription, - DescriptionLoD.SHORT, shortDescription); + Set getEventIDsWithHashHits(); - this.known = known; - this.hashHit = hashHit; - this.tagged = tagged; - this.dataSourceID = dataSourceID; + Set getEventIDsWithTags(); + + EventType getEventType(); + + long getEndMillis(); + + long getStartMillis(); + + default int getSize() { + return getEventIDs().size(); } - public boolean isTagged() { - return tagged; - } - - public boolean isHashHit() { - return hashHit; - } - - @Nullable - public Long getArtifactID() { - return artifactID; - } - - public long getEventID() { - return eventID; - } - - public long getFileID() { - return fileID; - } - - /** - * @return the time in seconds from unix epoch - */ - public long getTime() { - return time; - } - - public EventType getType() { - return subType; - } - - public String getFullDescription() { - return getDescription(DescriptionLoD.FULL); - } - - public String getMedDescription() { - return getDescription(DescriptionLoD.MEDIUM); - } - - public String getShortDescription() { - return getDescription(DescriptionLoD.SHORT); - } - - public TskData.FileKnown getKnown() { - return known; - } - - public String getDescription(DescriptionLoD lod) { - return descriptions.get(lod); - } - - public long getDataSourceID() { - return dataSourceID; - } + SortedSet getClusters(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index ed0357d6fa..996c35ef3b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -58,7 +58,7 @@ import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; -import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; +import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType; @@ -323,8 +323,8 @@ public class EventDB { return null; } - TimeLineEvent getEventById(Long eventID) { - TimeLineEvent result = null; + SingleEvent getEventById(Long eventID) { + SingleEvent result = null; DBLock.lock(); try { getEventByIDStmt.clearParameters(); @@ -939,8 +939,8 @@ public class EventDB { } } - private TimeLineEvent constructTimeLineEvent(ResultSet rs) throws SQLException { - return new TimeLineEvent(rs.getLong("event_id"), //NON-NLS + private SingleEvent constructTimeLineEvent(ResultSet rs) throws SQLException { + return new SingleEvent(rs.getLong("event_id"), //NON-NLS rs.getLong("datasource_id"), //NON-NLS rs.getLong("file_id"), //NON-NLS rs.getLong("artifact_id"), //NON-NLS @@ -1050,7 +1050,7 @@ public class EventDB { switch (Version.getBuildType()) { case DEVELOPMENT: - LOGGER.log(Level.INFO, "executing timeline query: {0}", query); //NON-NLS +// LOGGER.log(Level.INFO, "executing timeline query: {0}", query); //NON-NLS break; case RELEASE: default: @@ -1097,8 +1097,7 @@ public class EventDB { Set hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS Set tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS - return new EventCluster(interval, type, eventIDs, hashHits, tagged, - description, descriptionLOD); + return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); } /** @@ -1159,7 +1158,7 @@ public class EventDB { for (EventCluster eventCluster : aggEvents) { stripeDescMap.merge(ImmutablePair.of(eventCluster.getEventType(), eventCluster.getDescription()), - new EventStripe(eventCluster, null), EventStripe::merge); + new EventStripe(eventCluster), EventStripe::merge); } return stripeDescMap.values().stream().sorted(Comparator.comparing(EventStripe::getStartMillis)).collect(Collectors.toList()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 0583b0c175..fe770ce77a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -59,7 +59,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.CancellationProgressTask; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; +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; @@ -105,7 +105,7 @@ public class EventsRepository { private final LoadingCache maxCache; private final LoadingCache minCache; - private final LoadingCache idToEventCache; + private final LoadingCache idToEventCache; private final LoadingCache> eventCountsCache; private final LoadingCache> eventStripeCache; @@ -179,11 +179,11 @@ public class EventsRepository { } - public TimeLineEvent getEventById(Long eventID) { + public SingleEvent getEventById(Long eventID) { return idToEventCache.getUnchecked(eventID); } - synchronized public Set getEventsById(Collection eventIDs) { + synchronized public Set getEventsById(Collection eventIDs) { return eventIDs.stream() .map(idToEventCache::getUnchecked) .collect(Collectors.toSet()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java new file mode 100644 index 0000000000..2ff4474429 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java @@ -0,0 +1,15 @@ +/* + * 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.events; + +import java.util.Set; + +public class TagsAddedEvent extends TagsUpdatedEvent { + + public TagsAddedEvent(Set updatedEventIDs) { + super(updatedEventIDs); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java new file mode 100644 index 0000000000..8f7791910f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java @@ -0,0 +1,17 @@ +/* + * 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.events; + +import java.util.Set; + +public class TagsDeletedEvent extends TagsUpdatedEvent { + + public TagsDeletedEvent(Set updatedEventIDs) { + super(updatedEventIDs); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsUpdatedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsUpdatedEvent.java index 167069837c..7d38458332 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsUpdatedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsUpdatedEvent.java @@ -26,16 +26,14 @@ import java.util.Set; * been(un)tagged. This event is not intended for use out side of the timeline * module. */ -public class TagsUpdatedEvent { +abstract public class TagsUpdatedEvent { private final Set updatedEventIDs; - public ImmutableSet getUpdatedEventIDs() { return ImmutableSet.copyOf(updatedEventIDs); } - public TagsUpdatedEvent(Set updatedEventIDs) { this.updatedEventIDs = updatedEventIDs; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index 04b0370b42..abb7245eda 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -37,30 +37,30 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; +import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; /** - * * Explorer Node for {@link TimeLineEvent}s. + * * Explorer Node for {@link SingleEvent}s. */ class EventNode extends DisplayableItemNode { private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName()); - private final TimeLineEvent e; + private final SingleEvent e; - EventNode(TimeLineEvent eventById, AbstractFile file, BlackboardArtifact artifact) { + EventNode(SingleEvent eventById, AbstractFile file, BlackboardArtifact artifact) { super(Children.LEAF, Lookups.fixed(eventById, file, artifact)); this.e = eventById; - this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getType().getIconBase()); // NON-NLS + this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS } - EventNode(TimeLineEvent eventById, AbstractFile file) { + EventNode(SingleEvent eventById, AbstractFile file) { super(Children.LEAF, Lookups.fixed(eventById, file)); this.e = eventById; - this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getType().getIconBase()); // NON-NLS + this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS } @Override @@ -85,15 +85,15 @@ class EventNode extends DisplayableItemNode { properties.put(new NodeProperty<>("icon", "Icon", "icon", true)); // NON-NLS //gets overridden with icon properties.put(timePropery); properties.put(new NodeProperty<>("description", "Description", "description", e.getFullDescription())); // NON-NLS - properties.put(new NodeProperty<>("eventBaseType", "Base Type", "base type", e.getType().getSuperType().getDisplayName())); // NON-NLS - properties.put(new NodeProperty<>("eventSubType", "Sub Type", "sub type", e.getType().getDisplayName())); // NON-NLS + properties.put(new NodeProperty<>("eventBaseType", "Base Type", "base type", e.getEventType().getSuperType().getDisplayName())); // NON-NLS + properties.put(new NodeProperty<>("eventSubType", "Sub Type", "sub type", e.getEventType().getDisplayName())); // NON-NLS properties.put(new NodeProperty<>("Known", "Known", "known", e.getKnown().toString())); // NON-NLS return s; } private String getDateTimeString() { - return new DateTime(e.getTime() * 1000, DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter()); + return new DateTime(e.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter()); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 9ad3a2bb2f..e84c49922f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -32,7 +32,7 @@ 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.datamodel.TimeLineEvent; +import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -111,11 +111,11 @@ public class EventRootNode extends DisplayableItemNode { @Override protected Node createNodeForKey(Long eventID) { if (eventID >= 0) { - final TimeLineEvent eventById = filteredEvents.getEventById(eventID); + final SingleEvent eventById = filteredEvents.getEventById(eventID); try { AbstractFile file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(eventById.getFileID()); if (file != null) { - if (eventById.getType().getSuperType() == BaseTypes.FILE_SYSTEM) { + if (eventById.getEventType().getSuperType() == BaseTypes.FILE_SYSTEM) { return new EventNode(eventById, file); } else { BlackboardArtifact blackboardArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifact(eventById.getArtifactID()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index c51b3a82ed..6c9e1b00ed 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -60,7 +60,7 @@ public abstract class AbstractFilter implements Filter { @Override public boolean isDisabled() { - return disabled.get(); + return disabledProperty().get(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index bd88a6d90b..055c1c8748 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -64,8 +64,8 @@ public abstract class CompoundFilter extends Abstr } }); this.subFilters.setAll(subFilters); - - this.selectedProperty().addListener(activeProperty -> { + + this.selectedProperty().addListener(activeProperty -> { getSubFilters().forEach(subFilter -> subFilter.setDisabled(isActive() == false)); }); this.disabledProperty().addListener(activeProperty -> { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java index 0da318e80d..baeb3b7e61 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.filters; -import java.util.Comparator; +import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableBooleanValue; @@ -39,9 +39,9 @@ public class DataSourcesFilter extends UnionFilter { filterCopy.setSelected(isSelected()); filterCopy.setDisabled(isDisabled()); //add a copy of each subfilter - this.getSubFilters().forEach((DataSourceFilter t) -> { - filterCopy.addSubFilter(t.copyOf()); - }); + getSubFilters().forEach(dataSourceFilter -> + filterCopy.addSubFilter(dataSourceFilter.copyOf()) + ); return filterCopy; } @@ -66,13 +66,7 @@ public class DataSourcesFilter extends UnionFilter { } public void addSubFilter(DataSourceFilter dataSourceFilter) { - if (getSubFilters().stream().map(DataSourceFilter.class::cast) - .map(DataSourceFilter::getDataSourceID) - .filter(t -> t == dataSourceFilter.getDataSourceID()) - .findAny().isPresent() == false) { - getSubFilters().add(dataSourceFilter); - getSubFilters().sort(Comparator.comparing(DataSourceFilter::getDisplayName)); - } + super.addSubFilter(dataSourceFilter); if (getSubFilters().size() > 1) { setSelected(Boolean.TRUE); } @@ -106,4 +100,8 @@ public class DataSourcesFilter extends UnionFilter { return Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1)); } + @Override + Predicate getDuplicatePredicate(DataSourceFilter subfilter) { + return dataSourcefilter -> dataSourcefilter.getDataSourceID() == subfilter.getDataSourceID(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java index 371f32dcdc..c2eb196b79 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.filters; -import java.util.Comparator; +import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableBooleanValue; @@ -45,9 +45,9 @@ public class HashHitsFilter extends UnionFilter { filterCopy.setSelected(isSelected()); filterCopy.setDisabled(isDisabled()); //add a copy of each subfilter - this.getSubFilters().forEach((HashSetFilter t) -> { - filterCopy.addSubFilter(t.copyOf()); - }); + this.getSubFilters().forEach(hashSetFilter -> + filterCopy.addSubFilter(hashSetFilter.copyOf()) + ); return filterCopy; } @@ -86,18 +86,13 @@ public class HashHitsFilter extends UnionFilter { return areSubFiltersEqual(this, other); } - public void addSubFilter(HashSetFilter hashSetFilter) { - if (getSubFilters().stream() - .map(HashSetFilter::getHashSetID) - .filter(t -> t == hashSetFilter.getHashSetID()) - .findAny().isPresent() == false) { - getSubFilters().add(hashSetFilter); - getSubFilters().sort(Comparator.comparing(HashSetFilter::getDisplayName)); - } - } - @Override public ObservableBooleanValue disabledProperty() { return Bindings.or(super.disabledProperty(), Bindings.isEmpty(getSubFilters())); } + + @Override + Predicate getDuplicatePredicate(HashSetFilter subfilter) { + return hashSetFilter -> subfilter.getHashSetID() == hashSetFilter.getHashSetID(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java index ad6df42d85..f2d5b82a36 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashSetFilter.java @@ -84,5 +84,4 @@ public class HashSetFilter extends AbstractFilter { } 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 index 4c92a3b02a..973b82a19d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Comparator; +import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableBooleanValue; @@ -46,9 +47,9 @@ public class TagsFilter extends UnionFilter { filterCopy.setSelected(isSelected()); filterCopy.setDisabled(isDisabled()); //add a copy of each subfilter - this.getSubFilters().forEach((TagNameFilter t) -> { - filterCopy.addSubFilter(t.copyOf()); - }); + getSubFilters().forEach(tagNameFilter -> + filterCopy.addSubFilter(tagNameFilter.copyOf()) + ); return filterCopy; } @@ -87,17 +88,6 @@ public class TagsFilter extends UnionFilter { return areSubFiltersEqual(this, other); } - public void addSubFilter(TagNameFilter tagFilter) { - TagName newFilterTagName = tagFilter.getTagName(); - if (getSubFilters().stream() - .map(TagNameFilter::getTagName) - .filter(newFilterTagName::equals) - .findAny().isPresent() == false) { - getSubFilters().add(tagFilter); - } - getSubFilters().sort(Comparator.comparing(TagNameFilter::getDisplayName)); - } - public void removeFilterForTag(TagName tagName) { getSubFilters().removeIf(subfilter -> subfilter.getTagName().equals(tagName)); getSubFilters().sort(Comparator.comparing(TagNameFilter::getDisplayName)); @@ -107,4 +97,10 @@ public class TagsFilter extends UnionFilter { public ObservableBooleanValue disabledProperty() { return Bindings.or(super.disabledProperty(), Bindings.isEmpty(getSubFilters())); } + + @Override + Predicate getDuplicatePredicate(TagNameFilter subfilter) { + return tagNameFilter -> subfilter.getTagName().equals(tagNameFilter.getTagName()); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java index 4968e92e80..d3dc05985e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java @@ -18,7 +18,9 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import java.util.Comparator; import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.collections.FXCollections; import javafx.scene.image.Image; @@ -33,6 +35,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType; */ public class TypeFilter extends UnionFilter { + static private final Comparator comparator = Comparator.comparing(TypeFilter::getEventType, EventType.getComparator()); + /** * the event type this filter passes */ @@ -52,7 +56,7 @@ public class TypeFilter extends UnionFilter { if (recursive) { // add subfilters for each subtype for (EventType subType : et.getSubTypes()) { - this.getSubFilters().add(new TypeFilter(subType)); + addSubFilter(new TypeFilter(subType), comparator); } } } @@ -96,15 +100,15 @@ public class TypeFilter extends UnionFilter { @Override public TypeFilter copyOf() { //make a nonrecursive copy of this filter - final TypeFilter typeFilter = new TypeFilter(eventType, false); - typeFilter.setSelected(isSelected()); - typeFilter.setDisabled(isDisabled()); + final TypeFilter filterCopy = new TypeFilter(eventType, false); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); //add a copy of each subfilter - this.getSubFilters().forEach((TypeFilter t) -> { - typeFilter.getSubFilters().add(t.copyOf()); - }); + getSubFilters().forEach(typeFilter -> + filterCopy.addSubFilter(typeFilter.copyOf(), comparator) + ); - return typeFilter; + return filterCopy; } @Override @@ -142,4 +146,9 @@ public class TypeFilter extends UnionFilter { hash = 67 * hash + Objects.hashCode(this.eventType); return hash; } + + @Override + Predicate getDuplicatePredicate(TypeFilter subfilter) { + return t -> subfilter.getEventType().equals(t.eventType); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java index 27bec42c72..2aebb0cb32 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import java.util.Comparator; +import java.util.function.Predicate; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -34,5 +36,17 @@ abstract public class UnionFilter extends Compound super(FXCollections.observableArrayList()); } - + abstract Predicate getDuplicatePredicate(SubFilterType subfilter); + + public void addSubFilter(SubFilterType subfilter) { + addSubFilter(subfilter, Comparator.comparing(SubFilterType::getDisplayName)); + } + + protected void addSubFilter(SubFilterType subfilter, Comparator comparator) { + Predicate duplicatePredicate = getDuplicatePredicate(subfilter); + if (getSubFilters().stream().anyMatch(duplicatePredicate) == false) { + getSubFilters().add(subfilter); + } + getSubFilters().sort(comparator); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/eye_close.png b/Core/src/org/sleuthkit/autopsy/timeline/images/eye_close.png new file mode 100644 index 0000000000..01f8b9e501 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/eye_close.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/marker--arrow.png b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--arrow.png new file mode 100644 index 0000000000..e9c33c570f Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--arrow.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/marker--exclamation.png b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--exclamation.png new file mode 100644 index 0000000000..b225a567b3 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--exclamation.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/marker--minus.png b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--minus.png new file mode 100644 index 0000000000..2f1fda8b33 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--minus.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/marker--pencil.png b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--pencil.png new file mode 100644 index 0000000000..c18784e7cf Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--pencil.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/marker--pin.png b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--pin.png new file mode 100644 index 0000000000..d0e6a6a6bb Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--pin.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/marker--plus.png b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--plus.png new file mode 100644 index 0000000000..cb98edc78a Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/marker--plus.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java index eeefc35904..b0c4ef85d1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java @@ -45,8 +45,6 @@ import javafx.scene.control.Label; import javafx.scene.control.OverrunStyle; import javafx.scene.control.Tooltip; import javafx.scene.effect.Effect; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; @@ -82,7 +80,7 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; * {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up * common history context menu items out of derived classes? -jm */ -public abstract class AbstractVisualizationPane & TimeLineChart> extends BorderPane { +public abstract class AbstractVisualizationPane> extends BorderPane { @NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.") private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text()); @@ -132,6 +130,10 @@ public abstract class AbstractVisualizationPane settingsNodes; + public TimeLineController getController() { + return controller; + } + /** * @return the list of nodes containing settings widgets to insert into this * visualization's header @@ -195,6 +197,7 @@ public abstract class AbstractVisualizationPane getYAxis(); + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) abstract protected void resetData(); /** @@ -378,14 +381,6 @@ public abstract class AbstractVisualizationPane { - if (event.getButton() == MouseButton.PRIMARY && event.isStillSincePress()) { - selectedNodes.clear(); - } - }); - } - /** * add a {@link Text} node to the leaf container for the decluttered axis * labels @@ -512,12 +507,13 @@ public abstract class AbstractVisualizationPane { - setCenter(chart); //clear masker pane + setCenter(center); //clear masker pane setCursor(Cursor.DEFAULT); }); } @@ -543,11 +539,11 @@ public abstract class AbstractVisualizationPane { + resetData(); setDateAxisValues(axisValues); }); - } abstract protected void setDateAxisValues(AxisValuesType values); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/ContextMenuProvider.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/ContextMenuProvider.java new file mode 100644 index 0000000000..1710d5c734 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ContextMenuProvider.java @@ -0,0 +1,19 @@ +/* + * 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.ui; + +import javafx.scene.control.ContextMenu; +import javafx.scene.input.MouseEvent; +import org.sleuthkit.autopsy.timeline.TimeLineController; + +public interface ContextMenuProvider { + + TimeLineController getController(); + + void clearContextMenu(); + + ContextMenu getContextMenu(MouseEvent m); +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.fxml index a3885619d4..754b00e35d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.fxml @@ -1,12 +1,15 @@ - - - - - + + + + + + + + - + - - - -
- - - - - - - - - - - - - - - - - - -
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java index 2c5f1276fa..760f0a9bc3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -18,23 +18,20 @@ */ package org.sleuthkit.autopsy.timeline.ui.filtering; +import java.util.Arrays; import javafx.application.Platform; import javafx.beans.Observable; +import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ObservableMap; -import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; -import javafx.scene.control.ContextMenu; +import javafx.scene.control.Cell; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; -import javafx.scene.control.Menu; -import javafx.scene.control.MenuItem; import javafx.scene.control.SplitPane; import javafx.scene.control.TitledPane; -import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableRow; import javafx.scene.control.TreeTableView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; @@ -86,169 +83,71 @@ final public class FilterSetPanel extends BorderPane { @FXML private SplitPane splitPane; - private FilteredEventsModel filteredEvents; - - private TimeLineController controller; + private final FilteredEventsModel filteredEvents; + private final TimeLineController controller; private final ObservableMap expansionMap = FXCollections.observableHashMap(); - private double position; + private double dividerPosition; - @FXML @NbBundle.Messages({ - "Timeline.ui.filtering.menuItem.all=all", "FilterSetPanel.defaultButton.text=Default", - "Timeline.ui.filtering.menuItem.none=none", - "Timeline.ui.filtering.menuItem.only=only", - "Timeline.ui.filtering.menuItem.others=others", - "Timeline.ui.filtering.menuItem.select=select", - "FilterSetPanel.hiddenDescriptionsListView.unhideAndRm=Unhide and remove from list", - "FilterSetPanel.hiddenDescriptionsListView.remove=Remove from list", "FilsetSetPanel.hiddenDescriptionsPane.displayName=Hidden Descriptions"}) + @FXML void initialize() { assert applyButton != null : "fx:id=\"applyButton\" was not injected: check your FXML file 'FilterSetPanel.fxml'."; // NON-NLS ActionUtils.configureButton(new ApplyFiltersAction(), applyButton); - defaultButton.setText(Bundle.FilterSetPanel_defaultButton_text()); + ActionUtils.configureButton(new ResetFilters(Bundle.FilterSetPanel_defaultButton_text(), controller), defaultButton); + hiddenDescriptionsPane.setText(Bundle.FilsetSetPanel_hiddenDescriptionsPane_displayName()); //remove column headers via css. filterTreeTable.getStylesheets().addAll(FilterSetPanel.class.getResource("FilterTable.css").toExternalForm()); // NON-NLS //use row factory as hook to attach context menus to. - filterTreeTable.setRowFactory((TreeTableView param) -> { - final TreeTableRow row = new TreeTableRow<>(); - - MenuItem all = new MenuItem(Bundle.Timeline_ui_filtering_menuItem_all()); - all.setOnAction(e -> { - row.getTreeItem().getParent().getChildren().forEach((TreeItem t) -> { - t.getValue().setSelected(Boolean.TRUE); - }); - }); - MenuItem none = new MenuItem(Bundle.Timeline_ui_filtering_menuItem_none()); - none.setOnAction(e -> { - row.getTreeItem().getParent().getChildren().forEach((TreeItem t) -> { - t.getValue().setSelected(Boolean.FALSE); - }); - }); - - MenuItem only = new MenuItem(Bundle.Timeline_ui_filtering_menuItem_only()); - only.setOnAction(e -> { - row.getTreeItem().getParent().getChildren().forEach((TreeItem t) -> { - if (t == row.getTreeItem()) { - t.getValue().setSelected(Boolean.TRUE); - } else { - t.getValue().setSelected(Boolean.FALSE); - } - }); - }); - MenuItem others = new MenuItem(Bundle.Timeline_ui_filtering_menuItem_others()); - others.setOnAction(e -> { - row.getTreeItem().getParent().getChildren().forEach((TreeItem t) -> { - if (t == row.getTreeItem()) { - t.getValue().setSelected(Boolean.FALSE); - } else { - t.getValue().setSelected(Boolean.TRUE); - } - }); - }); - final ContextMenu rowMenu = new ContextMenu(); - Menu select = new Menu(Bundle.Timeline_ui_filtering_menuItem_select()); - select.setOnAction(e -> { - row.getItem().setSelected(!row.getItem().isSelected()); - }); - select.getItems().addAll(all, none, only, others); - rowMenu.getItems().addAll(select); - row.setContextMenu(rowMenu); - - return row; - }); + filterTreeTable.setRowFactory(ttv -> new FilterTreeTableRow()); //configure tree column to show name of filter and checkbox - treeColumn.setCellValueFactory(param -> param.getValue().valueProperty()); + treeColumn.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().valueProperty()); treeColumn.setCellFactory(col -> new FilterCheckBoxCellFactory<>().forTreeTable(col)); //configure legend column to show legend (or othe supplamantal ui, eg, text field for text filter) - legendColumn.setCellValueFactory(param -> param.getValue().valueProperty()); + legendColumn.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().valueProperty()); legendColumn.setCellFactory(col -> new LegendCell(this.controller)); expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true); - Action defaultFiltersAction = new ResetFilters(controller); - defaultButton.setOnAction(defaultFiltersAction); - defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty()); + this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> applyFilters()); + this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> applyFilters()); + this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> applyFilters()); - this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> { - applyFilters(); - }); - this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> { - applyFilters(); - }); - this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> { - applyFilters(); - }); - this.filteredEvents.filterProperty().addListener((Observable o) -> { - refresh(); - }); + this.filteredEvents.filterProperty().addListener((Observable o) -> refresh()); refresh(); hiddenDescriptionsListView.setItems(controller.getQuickHideFilters()); - hiddenDescriptionsListView.setCellFactory((ListView param) -> { - final ListCell forList = new FilterCheckBoxCellFactory().forList(); - - forList.itemProperty().addListener((Observable observable) -> { - if (forList.getItem() == null) { - forList.setContextMenu(null); - } else { - forList.setContextMenu(new ContextMenu(new MenuItem() { - { - forList.getItem().selectedProperty().addListener((observable, wasSelected, isSelected) -> { - configureText(isSelected); - }); - - configureText(forList.getItem().selectedProperty().get()); - setOnAction((ActionEvent event) -> { - controller.getQuickHideFilters().remove(forList.getItem()); - }); - } - - private void configureText(Boolean newValue) { - if (newValue) { - setText(Bundle.FilterSetPanel_hiddenDescriptionsListView_unhideAndRm()); - } else { - setText(Bundle.FilterSetPanel_hiddenDescriptionsListView_remove()); - } - } - })); - } - }); - - return forList; - }); + hiddenDescriptionsListView.setCellFactory(listView -> getNewDiscriptionFilterListCell()); controller.viewModeProperty().addListener(observable -> { applyFilters(); if (controller.viewModeProperty().get() == VisualizationMode.COUNTS) { - position = splitPane.getDividerPositions()[0]; + dividerPosition = splitPane.getDividerPositions()[0]; splitPane.setDividerPositions(1); hiddenDescriptionsPane.setExpanded(false); hiddenDescriptionsPane.setCollapsible(false); hiddenDescriptionsPane.setDisable(true); } else { - splitPane.setDividerPositions(position); + splitPane.setDividerPositions(dividerPosition); hiddenDescriptionsPane.setDisable(false); hiddenDescriptionsPane.setCollapsible(true); hiddenDescriptionsPane.setExpanded(true); hiddenDescriptionsPane.setCollapsible(false); - } }); - } public FilterSetPanel(TimeLineController controller) { this.controller = controller; this.filteredEvents = controller.getEventsModel(); FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS - } private void refresh() { @@ -257,23 +156,52 @@ final public class FilterSetPanel extends BorderPane { }); } + private void applyFilters() { + Platform.runLater(() -> { + controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); + }); + } + + private ListCell getNewDiscriptionFilterListCell() { + final ListCell cell = new FilterCheckBoxCellFactory().forList(); + cell.itemProperty().addListener(itemProperty -> { + if (cell.getItem() == null) { + cell.setContextMenu(null); + } else { + cell.setContextMenu(ActionUtils.createContextMenu(Arrays.asList( + new RemoveDescriptionFilterAction(controller, cell)) + )); + } + }); + return cell; + } + @NbBundle.Messages({"FilterSetPanel.applyButton.text=Apply", - "FilterSetPanel.applyButton.longText=(Re)Apply filters"}) + "FilterSetPanel.applyButton.longText=(Re)Apply filters"}) private class ApplyFiltersAction extends Action { ApplyFiltersAction() { super(Bundle.FilterSetPanel_applyButton_text()); setLongText(Bundle.FilterSetPanel_applyButton_longText()); setGraphic(new ImageView(TICK)); - setEventHandler((ActionEvent t) -> { - applyFilters(); - }); + setEventHandler(actionEvent -> applyFilters()); } } - private void applyFilters() { - Platform.runLater(() -> { - controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); - }); + @NbBundle.Messages({ + "FilterSetPanel.hiddenDescriptionsListView.unhideAndRemove=Unhide and remove from list", + "FilterSetPanel.hiddenDescriptionsListView.remove=Remove from list",}) + private static class RemoveDescriptionFilterAction extends Action { + + private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS + + RemoveDescriptionFilterAction(TimeLineController controller, Cell cell) { + super(actionEvent -> controller.getQuickHideFilters().remove(cell.getItem())); + setGraphic(new ImageView(SHOW)); + textProperty().bind( + Bindings.when(cell.getItem().selectedProperty()) + .then(Bundle.FilterSetPanel_hiddenDescriptionsListView_unhideAndRemove()) + .otherwise(Bundle.FilterSetPanel_hiddenDescriptionsListView_remove())); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeTableRow.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeTableRow.java new file mode 100644 index 0000000000..8b3f30be48 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeTableRow.java @@ -0,0 +1,104 @@ +/* + * 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.ui.filtering; + +import java.util.Arrays; +import java.util.function.BiPredicate; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeTableRow; +import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionGroup; +import org.controlsfx.control.action.ActionUtils; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.timeline.filters.Filter; + +/** + * + */ +class FilterTreeTableRow extends TreeTableRow { + + @Override + protected void updateItem(Filter item, boolean empty) { + super.updateItem(item, empty); + if (item == null || empty) { + setContextMenu(null); + } else { + setContextMenu(ActionUtils.createContextMenu(Arrays.asList(new SelectActionsGroup(this)))); + } + } + + @NbBundle.Messages(value = { + "Timeline.ui.filtering.menuItem.select=select", + "Timeline.ui.filtering.menuItem.all=all", + "Timeline.ui.filtering.menuItem.none=none", + "Timeline.ui.filtering.menuItem.only=only", + "Timeline.ui.filtering.menuItem.others=others"}) + private static enum SelectionAction { + ALL(Bundle.Timeline_ui_filtering_menuItem_all(), + (treeItem, row) -> true), + + NONE(Bundle.Timeline_ui_filtering_menuItem_none(), + (treeItem, row) -> false), + + ONLY(Bundle.Timeline_ui_filtering_menuItem_only(), + (treeItem, row) -> treeItem == row.getTreeItem()), + + OTHER(Bundle.Timeline_ui_filtering_menuItem_others(), + (treeItem, row) -> treeItem != row.getTreeItem()), + + SELECT(Bundle.Timeline_ui_filtering_menuItem_select(), + (treeItem, row) -> false == row.getItem().isSelected()); + + private final BiPredicate, TreeTableRow> selectionPredicate; + + private final String displayName; + + private SelectionAction(String displayName, BiPredicate, TreeTableRow> predicate) { + this.selectionPredicate = predicate; + this.displayName = displayName; + } + + public void doSelection(TreeItem treeItem, TreeTableRow row) { + treeItem.getValue().setSelected(selectionPredicate.test(treeItem, row)); + } + + public String getDisplayName() { + return displayName; + } + } + + private static final class SelectActionsGroup extends ActionGroup { + + SelectActionsGroup(TreeTableRow row) { + super(Bundle.Timeline_ui_filtering_menuItem_select(), + new Select(SelectionAction.ALL, row), + new Select(SelectionAction.NONE, row), + new Select(SelectionAction.ONLY, row), + new Select(SelectionAction.OTHER, row)); + setEventHandler(new Select(SelectionAction.SELECT, row)::handle); + } + } + + private static final class Select extends Action { + + public TreeTableRow getRow() { + return row; + } + private final TreeTableRow row; + private final SelectionAction selectionAction; + + Select(SelectionAction strategy, TreeTableRow row) { + super(strategy.getDisplayName()); + this.row = row; + this.selectionAction = strategy; + setEventHandler(actionEvent -> row.getTreeItem().getParent().getChildren().forEach(this::doSelection)); + } + + private void doSelection(TreeItem treeItem) { + selectionAction.doSelection(treeItem, getRow()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/utils/MappedList.java b/Core/src/org/sleuthkit/autopsy/timeline/utils/MappedList.java new file mode 100644 index 0000000000..9e2ab30ddd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/utils/MappedList.java @@ -0,0 +1,115 @@ +/* + * 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.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.TransformationList; + +/** + * + */ +public class MappedList extends TransformationList { + private final Function mapper; + + public MappedList(ObservableList source, Function mapper) { + super(source); + this.mapper = mapper; + } + + @Override + public int getSourceIndex(int index) { + return index; + } + + @Override + public E get(int index) { + return mapper.apply(getSource().get(index)); + } + + @Override + public int size() { + return getSource().size(); + } + + @Override + protected void sourceChanged(ListChangeListener.Change c) { + fireChange(new ListChangeListener.Change(this) { + @Override + public boolean wasAdded() { + return c.wasAdded(); + } + + @Override + public boolean wasRemoved() { + return c.wasRemoved(); + } + + @Override + public boolean wasReplaced() { + return c.wasReplaced(); + } + + @Override + public boolean wasUpdated() { + return c.wasUpdated(); + } + + @Override + public boolean wasPermutated() { + return c.wasPermutated(); + } + + @Override + public int getPermutation(int i) { + return c.getPermutation(i); + } + + @Override + protected int[] getPermutation() { + // This method is only called by the superclass methods + // wasPermutated() and getPermutation(int), which are + // both overriden by this class. There is no other way + // this method can be called. + throw new AssertionError("Unreachable code"); + } + + @Override + public List getRemoved() { + ArrayList res = new ArrayList<>(c.getRemovedSize()); + for (F e : c.getRemoved()) { + res.add(mapper.apply(e)); + } + return res; + } + + @Override + public int getFrom() { + return c.getFrom(); + } + + @Override + public int getTo() { + return c.getTo(); + } + + @Override + public boolean next() { + return c.next(); + } + + @Override + public void reset() { + c.reset(); + } + }); + } + +} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java index 627e32a46f..967fb4950c 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2012-2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -63,10 +63,10 @@ import org.sleuthkit.datamodel.*; class ExtractIE extends Extract { private static final Logger logger = Logger.getLogger(ExtractIE.class.getName()); - private IngestServices services = IngestServices.getInstance(); - private String moduleTempResultsDir; + private final IngestServices services = IngestServices.getInstance(); + private final String moduleTempResultsDir; private String PASCO_LIB_PATH; - private String JAVA_PATH; + private final String JAVA_PATH; private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); private Content dataSource; private IngestJobContext context; @@ -126,7 +126,7 @@ class ExtractIE extends Extract { datetime = Long.valueOf(Tempdate); String domain = Util.extractDomain(url); - Collection bbattributes = new ArrayList(); + Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "ExtractIE.parentModuleName.noSpace"), url)); @@ -153,13 +153,15 @@ class ExtractIE extends Extract { BufferedReader reader = new BufferedReader(new InputStreamReader(new ReadContentInputStream(fav))); String line, url = ""; try { - while ((line = reader.readLine()) != null) { + line = reader.readLine(); + while (null != line) { // The actual shortcut line we are interested in is of the // form URL=http://path/to/website if (line.startsWith("URL")) { //NON-NLS url = line.substring(line.indexOf("=") + 1); break; } + line = reader.readLine(); } } catch (IOException ex) { logger.log(Level.WARNING, "Failed to read from content: " + fav.getName(), ex); //NON-NLS @@ -231,7 +233,7 @@ class ExtractIE extends Extract { datetime = Long.valueOf(tempDate); String domain = Util.extractDomain(url); - Collection bbattributes = new ArrayList(); + Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "ExtractIE.parentModuleName.noSpace"), url)); @@ -377,6 +379,15 @@ class ExtractIE extends Extract { ProcessBuilder processBuilder = new ProcessBuilder(commandLine); processBuilder.redirectOutput(new File(outputFileFullPath)); processBuilder.redirectError(new File(errFileFullPath)); + /* + * NOTE on Pasco return codes: There is no documentation for Pasco. + * Looking at the Pasco source code I see that when something goes + * wrong Pasco returns a negative number as a return code. However, + * we should still attempt to parse the Pasco output even if that + * happens. I have seen many situations where Pasco output file + * contains a lot of useful data and only the last entry is + * corrupted. + */ ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context)); // @@@ Investigate use of history versus cache as type. } catch (IOException ex) { @@ -424,7 +435,7 @@ class ExtractIE extends Extract { } // Keep a list of reported user accounts to avoid repeats - Set reportedUserAccounts = new HashSet(); + Set reportedUserAccounts = new HashSet<>(); while (fileScanner.hasNext()) { String line = fileScanner.nextLine(); @@ -439,12 +450,11 @@ class ExtractIE extends Extract { continue; } - String ddtime = lineBuff[2]; String actime = lineBuff[3]; Long ftime = (long) 0; - String user = ""; - String realurl = ""; - String domain = ""; + String user; + String realurl; + String domain; /* * We've seen two types of lines: URL http://XYZ.com .... URL @@ -469,21 +479,15 @@ class ExtractIE extends Extract { domain = Util.extractDomain(realurl); - if (!ddtime.isEmpty()) { - ddtime = ddtime.replace("T", " "); //NON-NLS - ddtime = ddtime.substring(ddtime.length() - 5); - } - if (!actime.isEmpty()) { try { Long epochtime = dateFormatter.parse(actime).getTime(); - ftime = epochtime.longValue(); - ftime = ftime / 1000; + ftime = epochtime / 1000; } catch (ParseException e) { this.addErrorMessage( NbBundle.getMessage(this.getClass(), "ExtractIE.parsePascoOutput.errMsg.errParsingEntry", this.getName())); - logger.log(Level.SEVERE, "Error parsing Pasco results.", e); //NON-NLS + logger.log(Level.WARNING, String.format("Error parsing Pasco results, may have partial processing of corrupt file (id=%d)", origFile.getId()), e); //NON-NLS } } diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 1f081e06bd..99db8bfb85 100755 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -220,7 +220,7 @@ public class RegressionTest extends TestCase { JTableOperator jto = new JTableOperator(wo, 0); int row = jto.findCellRow("Hash Lookup", 1, 0); jto.clickOnCell(row, 1); - JButtonOperator jbo1 = new JButtonOperator(wo, "Advanced"); + JButtonOperator jbo1 = new JButtonOperator(wo, "Global Settings"); jbo1.pushNoBlock(); } @@ -260,7 +260,7 @@ public class RegressionTest extends TestCase { JTableOperator jto = new JTableOperator(wo, 0); int row = jto.findCellRow("Keyword Search", 1, 0); jto.clickOnCell(row, 1); - JButtonOperator jbo1 = new JButtonOperator(wo, "Advanced"); + JButtonOperator jbo1 = new JButtonOperator(wo, "Global Settings"); jbo1.pushNoBlock(); } diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index ffe39d6b4b..387cfb83e3 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 06 Apr 2016 23:58:02 -0400 +#Mon, 22 Feb 2016 16:37:47 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index aaaa4f0909..eac00c4b67 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Wed, 06 Apr 2016 23:58:02 -0400 +#Mon, 22 Feb 2016 16:37:47 -0500 CTL_MainWindow_Title=Autopsy 4.1.0 CTL_MainWindow_Title_No_Project=Autopsy 4.1.0