From 6400a72f3a58b6ca395b17f7223b3bc8e2e42804 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Wed, 19 Sep 2018 20:17:53 -0400 Subject: [PATCH] active listeners for tag and other events --- .../autopsy/timeline/FilteredEventsModel.java | 7 +- .../sleuthkit/autopsy/timeline/OnStart.java | 37 +++++ .../autopsy/timeline/OpenTimelineAction.java | 23 +--- .../autopsy/timeline/TimeLineController.java | 58 ++++---- .../autopsy/timeline/TimeLineModule.java | 127 ++++++++++++++++++ .../timeline/events/TagsAddedEvent.java | 1 + .../timeline/events/TagsDeletedEvent.java | 1 + 7 files changed, 198 insertions(+), 56 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/timeline/OnStart.java create mode 100755 Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/FilteredEventsModel.java index 057f44c2ad..cd3b4c8796 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/FilteredEventsModel.java @@ -644,8 +644,11 @@ public final class FilteredEventsModel { return updatedEventIDs; } - synchronized Set setFileStatus(AbstractFile file) throws TskCoreException { - Set updatedEventIDs = eventManager.setFileStatus(file); + synchronized public Set setHashHit(Collection artifacts, boolean hasHashHit) throws TskCoreException { + Set updatedEventIDs = new HashSet<>(); + for (BlackboardArtifact artifact : artifacts) { + updatedEventIDs.addAll(eventManager.setEventsHashed(artifact.getObjectID(), hasHashHit)); + } if (!updatedEventIDs.isEmpty()) { invalidateCaches(updatedEventIDs); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OnStart.java b/Core/src/org/sleuthkit/autopsy/timeline/OnStart.java new file mode 100755 index 0000000000..07ada6acf0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/OnStart.java @@ -0,0 +1,37 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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; + +/** + * The org.openide.modules.OnStart annotation tells NetBeans to invoke this + * class's run method. + */ +@org.openide.modules.OnStart +public class OnStart implements Runnable { + + /** + * This method is invoked by virtue of the OnStart annotation on the this + * class + */ + @Override + public void run() { + TimeLineModule.onStart(); + } +} + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 03ea78b33e..31fc5bec72 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -60,19 +60,10 @@ public final class OpenTimelineAction extends CallableSystemAction { private static final Logger logger = Logger.getLogger(OpenTimelineAction.class.getName()); private static final int FILE_LIMIT = 6_000_000; - private static TimeLineController timeLineController; - private final JMenuItem menuItem; private final JButton toolbarButton = new JButton(getName(), new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS - /** - * Invalidate the reference to the controller so that a new one will be - * instantiated the next time this action is invoked - */ - synchronized static void invalidateController() { - timeLineController = null; - } public OpenTimelineAction() { toolbarButton.addActionListener(actionEvent -> performAction()); @@ -95,11 +86,6 @@ public final class OpenTimelineAction extends CallableSystemAction { public void performAction() { if (tooManyFiles()) { Platform.runLater(PromptDialogManager::showTooManyFiles); - synchronized (OpenTimelineAction.this) { - if (timeLineController != null) { - timeLineController.shutDownTimeLine(); - } - } setEnabled(false); } else if ("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) { Platform.runLater(PromptDialogManager::showTimeLineDisabledMessage); @@ -125,13 +111,8 @@ public final class OpenTimelineAction extends CallableSystemAction { logger.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS return; } - if (timeLineController == null) { - timeLineController = new TimeLineController(currentCase); - } else if (timeLineController.getAutopsyCase() != currentCase) { - timeLineController.shutDownTimeLine(); - timeLineController = new TimeLineController(currentCase); - } - timeLineController.showTimeLine(file, artifact); + TimeLineController controller = TimeLineModule.getController(); + controller.showTimeLine(file, artifact); } catch (NoCurrentCaseException e) { //there is no case... Do nothing. } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 197b17e345..98b4fb60d9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -73,6 +73,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent; import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailViewEvent; import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState; @@ -86,6 +87,7 @@ import org.sleuthkit.datamodel.timeline.EventType; import org.sleuthkit.datamodel.timeline.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState; import org.sleuthkit.autopsy.timeline.zooming.TimeUnits; +import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.timeline.TimelineFilter.DescriptionFilter; import org.sleuthkit.datamodel.timeline.TimelineFilter.TypeFilter; @@ -194,9 +196,6 @@ public class TimeLineController { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private boolean listeningToAutopsy = false; - private final PropertyChangeListener caseListener = new AutopsyCaseListener(); - private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener(); - @GuardedBy("this") private final ReadOnlyObjectWrapper viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS); @@ -280,7 +279,7 @@ public class TimeLineController { return viewMode.get(); } - public TimeLineController(Case autoCase) throws TskCoreException { + TimeLineController(Case autoCase) throws TskCoreException { this.autoCase = autoCase; filteredEvents = new FilteredEventsModel(autoCase, currentParams.getReadOnlyProperty()); /* @@ -383,14 +382,10 @@ public class TimeLineController { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void shutDownTimeLine() { - listeningToAutopsy = false; - IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener); - Case.removePropertyChangeListener(caseListener); if (topComponent != null) { topComponent.close(); topComponent = null; } - OpenTimelineAction.invalidateController(); } /** @@ -403,12 +398,6 @@ public class TimeLineController { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void showTimeLine(AbstractFile file, BlackboardArtifact artifact) { - // listen for case changes (specifically images being added, and case changes). - if (Case.isCaseOpen() && !listeningToAutopsy) { - IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener); - Case.addPropertyChangeListener(caseListener); - listeningToAutopsy = true; - } Platform.runLater(() -> { //if there is an existing prompt or progressdialog,... if (promptDialogManager.bringCurrentDialogToFront()) { @@ -726,14 +715,8 @@ public class TimeLineController { } - /** - * Listener for IngestManager.IngestModuleEvents. - */ - @Immutable - private class AutopsyIngestModuleListener implements PropertyChangeListener { - @Override - public void propertyChange(PropertyChangeEvent evt) { + void handleIngestModuleEvent(PropertyChangeEvent evt) { /** * Checking for a current case is a stop gap measure until a * different way of handling the closing of cases is worked out. @@ -746,30 +729,39 @@ public class TimeLineController { // Case is closed, do nothing. return; } + + // ignore remote events. The node running the ingest should update the Case DB + // @@@ We should signal though that there is more data and flush caches... + if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) { + return; + } switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { case CONTENT_CHANGED: + // new files were already added to the events table from SleuthkitCase. + break; case DATA_ADDED: + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { + executor.submit(() -> filteredEvents.setHashHit(eventData.getArtifacts(), true)); + } break; case FILE_DONE: /* * Since the known state or hash hit state may have changed * invalidate caches. */ - executor.submit(filteredEvents::invalidateAllCaches); + //@@@ This causes HUGE slow downs during ingest when TL is open. + // executor.submit(filteredEvents::invalidateAllCaches); + + // known state should have been udpated automatically via SleuthkitCase.setKnown(); + // hashes should have been updated from event } } - } - /** - * Listener for Case.Events - */ - @Immutable - private class AutopsyCaseListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - switch (Case.Events.valueOf(evt.getPropertyName())) { + + void handleCaseEvent(PropertyChangeEvent evt) { + switch (Case.Events.valueOf(evt.getPropertyName())) { case BLACKBOARD_ARTIFACT_TAG_ADDED: executor.submit(() -> filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt)); break; @@ -793,6 +785,6 @@ public class TimeLineController { executor.submit(filteredEvents::invalidateAllCaches); break; } - } } } + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java new file mode 100755 index 0000000000..c8856a0dd4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java @@ -0,0 +1,127 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javafx.application.Platform; +import org.sleuthkit.autopsy.casemodule.Case; +import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Manages listeners and the controller. + * + */ +public class TimeLineModule { + + private static final Logger logger = Logger.getLogger(TimeLineModule.class.getName()); + + private static final Object controllerLock = new Object(); + private static TimeLineController controller; + + /** + * provides static utilities, can not be instantiated + */ + private TimeLineModule() { + } + + /** + * Get instance of the controller for the current case + * @return + * @throws NoCurrentCaseException + */ + public static TimeLineController getController() throws NoCurrentCaseException { + synchronized (controllerLock) { + if (controller == null) { + try { + controller = new TimeLineController(Case.getCurrentCaseThrows()); + } catch (NoCurrentCaseException | TskCoreException ex) { + throw new NoCurrentCaseException("Error getting TimeLineController for the current case.", ex); + } + } + return controller; + } + } + + /** + * This method is invoked by virtue of the OnStart annotation on the OnStart + * class class + */ + static void onStart() { + Platform.setImplicitExit(false); + logger.info("Setting up TimeLine listeners"); //NON-NLS + + IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener()); + Case.addPropertyChangeListener(new CaseEventListener()); + } + + /** + * Listener for case events. + */ + static private class CaseEventListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + try { + TimeLineController tlController = getController(); + tlController.handleCaseEvent(evt); + } catch (NoCurrentCaseException ex) { + // ignore + return; + } + + switch (Case.Events.valueOf(evt.getPropertyName())) { + case CURRENT_CASE: + // we care only about case closing here + if (evt.getNewValue() != null) { + break; + } + synchronized (controllerLock) { + if (controller != null) { + controller.shutDownTimeLine(); + } + controller = null; + } + break; + } + } + } + + /** + * Listener for IngestModuleEvents + */ + static private class IngestModuleEventListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + try { + TimeLineController tlController = getController(); + tlController.handleIngestModuleEvent(evt); + } catch (NoCurrentCaseException ex) { + // ignore + return; + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java index 0917513430..a793644767 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsAddedEvent.java @@ -22,6 +22,7 @@ import java.util.Set; /** * A TagsUpdatedEvent for tags that have been added to events. + * NOTE: This event is internal to timeline components */ public class TagsAddedEvent extends TagsUpdatedEvent { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java index f0d5826060..5f53ccba9e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/TagsDeletedEvent.java @@ -22,6 +22,7 @@ import java.util.Set; /** * A TagsUpdatedEvent for tags that have been removed from events. + * NOTE: This event is internal to timeline components */ public class TagsDeletedEvent extends TagsUpdatedEvent {