diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index 03f6651654479baa3cec364b3f1b75e8381aeda4..37a171fdf9e46dfea4762dfb97865817e3633188 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -82,6 +82,7 @@ import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType; import org.sleuthkit.datamodel.IngestModuleInfo.IngestModuleType; import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess; +import org.sleuthkit.datamodel.TimelineManager.TimelineEventAddedEvent; import org.sleuthkit.datamodel.TskData.DbType; import org.sleuthkit.datamodel.TskData.FileKnown; import org.sleuthkit.datamodel.TskData.ObjectType; @@ -13939,6 +13940,7 @@ public static final class CaseDbTransaction { // Score changes are stored as a map keyed by objId to prevent duplicates. private Map<Long, ScoreChange> scoreChangeMap = new HashMap<>(); private List<Host> hostsAdded = new ArrayList<>(); + private List<TimelineEventAddedEvent> timelineEvents = new ArrayList<>(); private List<OsAccount> accountsChanged = new ArrayList<>(); private List<OsAccount> accountsAdded = new ArrayList<>(); private List<TskEvent.MergedAccountsPair> accountsMerged = new ArrayList<>(); @@ -13986,6 +13988,16 @@ CaseDbConnection getConnection() { void registerScoreChange(ScoreChange scoreChange) { scoreChangeMap.put(scoreChange.getObjectId(), scoreChange); } + + /** + * Register timeline event to be fired when transaction finishes. + * @param timelineEvent The timeline event. + */ + void registerTimelineEvent(TimelineEventAddedEvent timelineEvent) { + if (timelineEvent != null) { + timelineEvents.add(timelineEvent); + } + } /** * Saves a host that has been added as a part of this transaction. @@ -14084,6 +14096,11 @@ public void commit() throws TskCoreException { sleuthkitCase.fireTSKEvent(new TskEvent.AggregateScoresChangedEvent(entry.getKey(), ImmutableSet.copyOf(entry.getValue()))); } } + if (!timelineEvents.isEmpty()) { + for (TimelineEventAddedEvent evt : timelineEvents) { + sleuthkitCase.fireTSKEvent(evt); + } + } if (!hostsAdded.isEmpty()) { sleuthkitCase.fireTSKEvent(new TskEvent.HostsAddedTskEvent(hostsAdded)); } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java index cf560705d3ad91e16ead90f9087e334191a62ec2..0b215868d60e5d045037766c16c7dee15ea31f7f 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java @@ -26,6 +26,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; +import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; @@ -48,6 +49,7 @@ import static org.sleuthkit.datamodel.CollectionUtils.isNotEmpty; import static org.sleuthkit.datamodel.CommManagerSqlStringUtils.buildCSVString; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import static org.sleuthkit.datamodel.SleuthkitCase.escapeSingleQuotes; /** @@ -790,6 +792,58 @@ private Optional<TimelineEvent> addOtherEventDesc(BlackboardArtifact artifact) t return addArtifactEvent(evtWDesc, evtType, artifact); } + + + /** + * Adds a timeline event to the database in a transaction. + * @param eventType The event type. + * @param shortDesc The short description. + * @param medDesc The medium description. + * @param longDesc The long description. + * @param dataSourceId The data source id of the event. + * @param contentId The content id of the event. + * @param artifactId The artifact id of the event (can be null). + * @param time Unix epoch offset time of the event in seconds. + * @param hashHit True if a hash hit. + * @param tagged True if tagged. + * @param trans The transaction. + * @return The added event. + * @throws TskCoreException + */ + @Beta + public TimelineEvent addTimelineEvent( + TimelineEventType eventType, String shortDesc, String medDesc, String longDesc, + long dataSourceId, long contentId, Long artifactId, long time, + boolean hashHit, boolean tagged, + CaseDbTransaction trans + ) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try { + Long descriptionID = addEventDescription(dataSourceId, contentId, artifactId, + longDesc, medDesc, shortDesc, hashHit, tagged, trans.getConnection()); + + if (descriptionID == null) { + descriptionID = getEventDescription(dataSourceId, contentId, artifactId, longDesc, trans.getConnection()); + } + if (descriptionID != null) { + long eventID = addEventWithExistingDescription(time, eventType, descriptionID, trans.getConnection()); + TimelineEvent timelineEvt = new TimelineEvent(eventID, descriptionID, contentId, artifactId, time, eventType, + longDesc, medDesc, shortDesc, hashHit, tagged); + + trans.registerTimelineEvent(new TimelineEventAddedEvent(timelineEvt)); + return timelineEvt; + } else { + throw new TskCoreException(MessageFormat.format( + "Failed to get event description for [shortDesc: {0}, dataSourceId: {1}, contentId: {2}, artifactId: {3}]", + shortDesc, dataSourceId, contentId, artifactId == null ? "<null>" : artifactId)); + } + } catch (DuplicateException dupEx) { + logger.log(Level.WARNING, "Attempt to make duplicate", dupEx); + return null; + } finally { + caseDB.releaseSingleUserCaseWriteLock(); + } + } /** * Add an event of the given type from the given artifact to the database.