diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java index 490df9a481..cd56754579 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java @@ -31,6 +31,7 @@ import org.apache.commons.collections.CollectionUtils; import org.joda.time.DateTime; import org.joda.time.Interval; import org.openide.util.NbBundle.Messages; +import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineDataSourceUtils; @@ -64,7 +65,7 @@ import org.sleuthkit.datamodel.TskCoreException; "TimlinePanel_last30DaysChart_fileEvts_title=File Events", "TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",}) public class TimelinePanel extends BaseDataSourceSummaryPanel { - + private static final Logger logger = Logger.getLogger(TimelinePanel.class.getName()); private static final long serialVersionUID = 1L; private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy"); @@ -86,7 +87,6 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title()); private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title()); private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", ""); - private final OpenTimelineAction openTimelineAction = new OpenTimelineAction(); private final TimelineDataSourceUtils timelineUtils = TimelineDataSourceUtils.getInstance(); // all loadable components on this tab @@ -94,7 +94,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { // actions to load data for this tab private final List> dataFetchComponents; - + public TimelinePanel() { this(new TimelineSummary()); } @@ -109,7 +109,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT), (result) -> handleResult(result)) ); - + initComponents(); } @@ -125,7 +125,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { private static String formatDate(Date date, DateFormat formatter) { return date == null ? null : formatter.format(date); } - + private static final Color FILE_EVT_COLOR = new Color(228, 22, 28); private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100); @@ -145,25 +145,25 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { // Create a bar chart item for each recent days activity item List fileEvtCounts = new ArrayList<>(); List artifactEvtCounts = new ArrayList<>(); - + for (int i = 0; i < recentDaysActivity.size(); i++) { DailyActivityAmount curItem = recentDaysActivity.get(i); - + long fileAmt = curItem.getFileActivityCount(); long artifactAmt = curItem.getArtifactActivityCount() * 100; String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1) ? formatDate(curItem.getDay(), CHART_FORMAT) : ""; - + OrderedKey thisKey = new OrderedKey(formattedDate, i); fileEvtCounts.add(new BarChartItem(thisKey, fileAmt)); artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt)); } - + return Arrays.asList( new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts), new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts)); } - + private final Object timelineBtnLock = new Object(); private TimelineSummaryData curTimelineData = null; @@ -179,11 +179,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT))); latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT))); last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity()))); - + if (result != null && result.getResultType() == DataFetchResult.ResultType.SUCCESS && result.getData() != null) { - + synchronized (this.timelineBtnLock) { this.curTimelineData = result.getData(); this.viewInTimelineBtn.setEnabled(true); @@ -208,7 +208,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { if (curTimelineData == null) { return; } - + dataSource = curTimelineData.getDataSource(); if (CollectionUtils.isNotEmpty(curTimelineData.getMostRecentDaysActivity())) { minDate = curTimelineData.getMostRecentDaysActivity().get(0).getDay(); @@ -219,38 +219,59 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { } } } + + openFilteredChart(dataSource, minDate, maxDate); + } + + /** + * Action that occurs when 'View in Timeline' button is pressed. + * + * @param dataSource The data source to filter to. + * @param minDate The min date for the zoom of the window. + * @param maxDate The max date for the zoom of the window. + */ + private void openFilteredChart(DataSource dataSource, Date minDate, Date maxDate) { + OpenTimelineAction openTimelineAction = CallableSystemAction.get(OpenTimelineAction.class); + if (openTimelineAction == null) { + logger.log(Level.WARNING, "No OpenTimelineAction provided by CallableSystemAction; taking no redirect action."); + } // notify dialog (if in dialog) should close. TimelinePanel.this.notifyParentClose(); - - // open the timeline filtered to data source and zoomed in on interval - openTimelineAction.performAction(); + + Interval timeSpan = null; + try { - TimeLineController controller = TimeLineModule.getController(); + final TimeLineController controller = TimeLineModule.getController(); + if (dataSource != null) { controller.pushFilters(timelineUtils.getDataSourceFilterState(dataSource)); } - + if (minDate != null && maxDate != null) { - Interval timeSpan = new Interval(new DateTime(minDate), new DateTime(maxDate)); - controller.pushTimeRange(timeSpan); + timeSpan = new Interval(new DateTime(minDate), new DateTime(maxDate)); } - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.WARNING, "Unable to open Timeline view", ex); + logger.log(Level.WARNING, "Unable to view time range in Timeline view", ex); + } + + try { + openTimelineAction.showTimeline(timeSpan); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "An unexpected exception occurred while opening the timeline.", ex); } } - + @Override protected void fetchInformation(DataSource dataSource) { fetchInformation(dataFetchComponents, dataSource); } - + @Override protected void onNewDataSource(DataSource dataSource) { onNewDataSource(dataFetchComponents, loadableComponents, dataSource); } - + @Override public void close() { ingestRunningLabel.unregister(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index 202e521dc2..fdd45d14a1 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -24,6 +24,7 @@ import javafx.application.Platform; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JMenuItem; +import org.joda.time.Interval; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; @@ -46,13 +47,10 @@ import org.sleuthkit.datamodel.TskCoreException; * An Action that opens the Timeline window. Has methods to open the window in * various specific states (e.g., showing a specific artifact in the List View) */ - - @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false) @ActionReferences(value = { - @ActionReference(path = "Menu/Tools", position = 104) - , + @ActionReference(path = "Menu/Tools", position = 104), @ActionReference(path = "Toolbars/Case", position = 104)}) public final class OpenTimelineAction extends CallableSystemAction { @@ -64,7 +62,6 @@ public final class OpenTimelineAction extends CallableSystemAction { private final JButton toolbarButton = new JButton(getName(), new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS - public OpenTimelineAction() { toolbarButton.addActionListener(actionEvent -> performAction()); menuItem = super.getMenuPresenter(); @@ -74,9 +71,10 @@ public final class OpenTimelineAction extends CallableSystemAction { @Override public boolean isEnabled() { /** - * We used to also check if Case.getCurrentOpenCase().hasData() was true. We - * disabled that check because if it is executed while a data source is - * being added, it blocks the edt. We still do that in ImageGallery. + * We used to also check if Case.getCurrentOpenCase().hasData() was + * true. We disabled that check because if it is executed while a data + * source is being added, it blocks the edt. We still do that in + * ImageGallery. */ return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); } @@ -103,7 +101,7 @@ public final class OpenTimelineAction extends CallableSystemAction { @NbBundle.Messages({ "OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.", "OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."}) - synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException { + synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact, Interval timeSpan) throws TskCoreException { try { Case currentCase = Case.getCurrentCaseThrows(); if (currentCase.hasData() == false) { @@ -112,6 +110,15 @@ public final class OpenTimelineAction extends CallableSystemAction { return; } TimeLineController controller = TimeLineModule.getController(); + // if file or artifact not specified, specify the time range as either + // a) full range if timeSpan is null or b) the timeSpan + if (file == null && artifact == null) { + if (timeSpan == null) { + controller.showFullRange(); + } else { + controller.pushTimeRange(timeSpan); + } + } controller.showTimeLine(file, artifact); } catch (NoCurrentCaseException e) { //there is no case... Do nothing. @@ -123,7 +130,18 @@ public final class OpenTimelineAction extends CallableSystemAction { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void showTimeline() throws TskCoreException { - showTimeline(null, null); + showTimeline(null, null, null); + } + + /** + * Open timeline with the given timeSpan time range. + * + * @param timeSpan The time range to display. + * @throws TskCoreException + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public void showTimeline(Interval timeSpan) throws TskCoreException { + showTimeline(null, null, timeSpan); } /** @@ -135,7 +153,7 @@ public final class OpenTimelineAction extends CallableSystemAction { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void showFileInTimeline(AbstractFile file) throws TskCoreException { - showTimeline(file, null); + showTimeline(file, null, null); } /** @@ -146,7 +164,7 @@ public final class OpenTimelineAction extends CallableSystemAction { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException { - showTimeline(null, artifact); + showTimeline(null, artifact, null); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 0c80cb5315..80dba865f5 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -62,7 +62,6 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; -import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; @@ -348,7 +347,7 @@ public class TimeLineController { /** * Show the entire range of the timeline. */ - private boolean showFullRange() throws TskCoreException { + boolean showFullRange() throws TskCoreException { synchronized (filteredEvents) { return pushTimeRange(filteredEvents.getSpanningInterval()); } @@ -359,7 +358,7 @@ public class TimeLineController { * ViewInTimelineRequestedEvent in the List View. * * @param requestEvent Contains the ID of the requested events and the - * timerange to show. + * timerange to show. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void showInListView(ViewInTimelineRequestedEvent requestEvent) throws TskCoreException { @@ -376,14 +375,13 @@ public class TimeLineController { } } - /** * Shuts down the task executor in charge of handling case events. */ void shutDownTimeLineListeners() { ThreadUtils.shutDownTaskExecutor(executor); } - + /** * "Shut down" Timeline. Close the timeline window. */ @@ -394,15 +392,13 @@ public class TimeLineController { topComponent = null; } } - - /** * Add the case and ingest listeners, prompt for rebuilding the database if * necessary, and show the timeline window. * - * @param file The AbstractFile from which to choose an event to show in - * the List View. + * @param file The AbstractFile from which to choose an event to show in the + * List View. * @param artifact The BlackboardArtifact to show in the List View. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -421,9 +417,7 @@ public class TimeLineController { try { if (file == null && artifact == null) { SwingUtilities.invokeLater(TimeLineController.this::showWindow); - this.showFullRange(); } else { - //prompt user to pick specific event and time range ShowInTimelineDialog showInTimelineDilaog = (file == null) ? new ShowInTimelineDialog(this, artifact) @@ -453,7 +447,7 @@ public class TimeLineController { * around the middle of the currently viewed time range. * * @param period The period of time to show around the current center of the - * view. + * view. */ synchronized public void pushPeriod(ReadablePeriod period) throws TskCoreException { synchronized (filteredEvents) { @@ -496,8 +490,8 @@ public class TimeLineController { */ topComponent.requestActive(); } - - synchronized public TimeLineTopComponent getTopComponent(){ + + synchronized public TimeLineTopComponent getTopComponent() { return topComponent; } @@ -517,7 +511,7 @@ public class TimeLineController { * @param timeRange The Interval to view. * * @return True if the interval was changed. False if the interval was the - * same as the existing one and no change happened. + * same as the existing one and no change happened. */ synchronized public boolean pushTimeRange(Interval timeRange) throws TskCoreException { //clamp timerange to case @@ -709,7 +703,7 @@ public class TimeLineController { * Register the given object to receive events. * * @param listener The object to register. Must implement public methods - * annotated with Subscribe. + * annotated with Subscribe. */ synchronized public void registerForEvents(Object listener) { eventbus.register(listener);