diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index c6e995c195..0929608b12 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -29,6 +29,7 @@ import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.awt.ActionRegistration; +import org.openide.util.Exceptions; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; @@ -50,7 +51,8 @@ import org.sleuthkit.datamodel.TskCoreException; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false) @ActionReferences(value = { - @ActionReference(path = "Menu/Tools", position = 102), + @ActionReference(path = "Menu/Tools", position = 102) + , @ActionReference(path = "Toolbars/Case", position = 102)}) public final class OpenTimelineAction extends CallableSystemAction { @@ -99,18 +101,23 @@ public final class OpenTimelineAction extends CallableSystemAction { } } setEnabled(false); - }else if("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) { + } else if ("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) { Platform.runLater(PromptDialogManager::showTimeLineDisabledMessage); setEnabled(false); - }else { - showTimeline(); + } else { + try { + showTimeline(); + } catch (TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.OpenTimelineAction_settingsErrorMessage()); + logger.log(Level.SEVERE, "Error showingtimeline.", ex); + } } } @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) { + synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException { try { Case currentCase = Case.getOpenCase(); if (currentCase.hasData() == false) { @@ -118,20 +125,13 @@ public final class OpenTimelineAction extends CallableSystemAction { logger.log(Level.INFO, "Could not create timeline, there are no data sources.");// NON-NLS return; } - try { - if (timeLineController == null) { - timeLineController = new TimeLineController(currentCase); - } else if (timeLineController.getAutopsyCase() != currentCase) { - timeLineController.shutDownTimeLine(); - timeLineController = new TimeLineController(currentCase); - } - - timeLineController.showTimeLine(file, artifact); - - } catch (IOException iOException) { - MessageNotifyUtil.Message.error(Bundle.OpenTimelineAction_settingsErrorMessage()); - logger.log(Level.SEVERE, "Failed to initialize per case timeline settings.", iOException); + if (timeLineController == null) { + timeLineController = new TimeLineController(currentCase); + } else if (timeLineController.getAutopsyCase() != currentCase) { + timeLineController.shutDownTimeLine(); + timeLineController = new TimeLineController(currentCase); } + timeLineController.showTimeLine(file, artifact); } catch (NoCurrentCaseException e) { //there is no case... Do nothing. } @@ -141,7 +141,7 @@ public final class OpenTimelineAction extends CallableSystemAction { * Open the Timeline window with the default initial view. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void showTimeline() { + public void showTimeline() throws TskCoreException { showTimeline(null, null); } @@ -153,7 +153,7 @@ public final class OpenTimelineAction extends CallableSystemAction { * @param file The AbstractFile to show in the Timeline. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void showFileInTimeline(AbstractFile file) { + public void showFileInTimeline(AbstractFile file) throws TskCoreException { showTimeline(file, null); } @@ -164,7 +164,7 @@ public final class OpenTimelineAction extends CallableSystemAction { * @param artifact The BlackboardArtifact to show in the Timeline. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void showArtifactInTimeline(BlackboardArtifact artifact) { + public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException { showTimeline(null, artifact); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java index 6861dac340..d2eb23ff16 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java @@ -28,6 +28,7 @@ import java.util.Objects; import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.TskCoreException; /** * Provides access to per-case timeline properties (key-value store). @@ -54,18 +55,20 @@ class PerCaseTimelineProperties { * * @throws IOException if there is a problem reading the state from disk */ - public synchronized boolean isDBStale() throws IOException { - - String stale = getProperty(STALE_KEY); - return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); - + public synchronized boolean isDBStale() throws TskCoreException { + try { + String stale = getProperty(STALE_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + } catch (IOException iOException) { + throw new TskCoreException("Error reading staleness of timeline DB", iOException); + } } /** * record the state of the events db as stale(true) or not stale(false). * * @param stale the new state of the event db. true for stale, false for not - * stale. + * stale. * * @throws IOException if there was a problem writing the state to disk. */ @@ -131,7 +134,7 @@ class PerCaseTimelineProperties { /** * Sets the given property to the given value. * - * @param propertyKey - The key of the property to be modified. + * @param propertyKey - The key of the property to be modified. * @param propertyValue - the value to set the property to. * * @throws IOException if there was a problem writing the property to disk diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index 835677aac1..25d0c0e6b1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -206,7 +206,7 @@ final class ShowInTimelineDialog extends Dialog { * @param artifact The BlackboardArtifact to configure this dialog for. */ @NbBundle.Messages({"ShowInTimelineDialog.artifactTitle=View Result in Timeline."}) - ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) { + ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) throws TskCoreException { //get events IDs from artifact this(controller, controller.getEventsModel().getEventIDsForArtifact(artifact)); @@ -236,7 +236,7 @@ final class ShowInTimelineDialog extends Dialog { @NbBundle.Messages({"# {0} - file path", "ShowInTimelineDialog.fileTitle=View {0} in timeline.", "ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."}) - ShowInTimelineDialog(TimeLineController controller, AbstractFile file) { + ShowInTimelineDialog(TimeLineController controller, AbstractFile file) throws TskCoreException { this(controller, controller.getEventsModel().getEventIDsForFile(file, false)); /* diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index cdc4c87624..03bf1efdd9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -54,6 +54,7 @@ import javafx.concurrent.Task; import javafx.concurrent.Worker; import static javafx.concurrent.Worker.State.FAILED; import static javafx.concurrent.Worker.State.SUCCEEDED; +import javafx.scene.control.Alert; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.swing.SwingUtilities; @@ -63,6 +64,7 @@ import org.joda.time.Interval; import org.joda.time.ReadablePeriod; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; @@ -81,7 +83,7 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent.CANCELLED; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.db.EventsRepository; +import org.sleuthkit.autopsy.timeline.datamodel.EventsRepository; import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent; import org.sleuthkit.datamodel.timeline.filters.DescriptionFilter; import org.sleuthkit.datamodel.timeline.filters.RootFilter; @@ -90,6 +92,7 @@ import org.sleuthkit.datamodel.timeline.DescriptionLoD; import org.sleuthkit.datamodel.timeline.ZoomParams; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.timeline.EventType; import org.sleuthkit.datamodel.timeline.EventTypeZoomLevel; import org.sleuthkit.datamodel.timeline.IntervalUtils; @@ -116,7 +119,7 @@ import org.sleuthkit.datamodel.timeline.TimeUnits; "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"}) public class TimeLineController { - private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName()); + private static final Logger logger = Logger.getLogger(TimeLineController.class.getName()); private static final ReadOnlyObjectWrapper timeZone = new ReadOnlyObjectWrapper<>(TimeZone.getDefault()); @@ -292,7 +295,7 @@ public class TimeLineController { MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(), stale ? Bundle.TimeLineController_setEventsDBStale_errMsgStale() : Bundle.TimeLineController_setEventsDBStale_errMsgNotStale()); - LOGGER.log(Level.SEVERE, "Error marking the timeline db as stale.", ex); //NON-NLS + logger.log(Level.SEVERE, "Error marking the timeline db as stale.", ex); //NON-NLS } } @@ -328,7 +331,7 @@ public class TimeLineController { return viewMode.get(); } - public TimeLineController(Case autoCase) throws IOException { + public TimeLineController(Case autoCase) throws TskCoreException { this.autoCase = autoCase; this.perCaseTimelineProperties = new PerCaseTimelineProperties(autoCase); eventsDBStale.set(perCaseTimelineProperties.isDBStale()); @@ -354,7 +357,13 @@ public class TimeLineController { historyManager.advance(InitialZoomState); //clear the selected events when the view mode changes - viewMode.addListener(observable -> selectEventIDs(Collections.emptySet())); + viewMode.addListener(observable -> { + try { + selectEventIDs(Collections.emptySet()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error clearing the timeline selection.", ex); + } + }); } /** @@ -368,7 +377,7 @@ public class TimeLineController { pushFilters(filteredEvents.getDefaultFilter()); } - public void zoomOutToActivity() { + public void zoomOutToActivity() throws TskCoreException { Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval(getJodaTimeZone()); advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval)); } @@ -395,18 +404,16 @@ public class TimeLineController { * either file or artifact is not null the user will be prompted to choose a * derived event and time range to show in the Timeline List View. * - * @param repoBuilder A Function from Consumer to - * CancellationProgressTask. Ie a function that - * given a worker state listener, produces a task with - * that listener attached. Expected to be a method - * reference to either - * EventsRepository.rebuildRepository() or - * EventsRepository.rebuildTags() + * @param repoBuilder A Function from Consumer to + * CancellationProgressTask. Ie a function that given a worker state + * listener, produces a task with that listener attached. Expected to be a + * method reference to either EventsRepository.rebuildRepository() or + * EventsRepository.rebuildTags() * @param markDBNotStale After the repo is rebuilt should it be marked not - * stale - * @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. + * stale + * @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.JFX) @NbBundle.Messages({ @@ -443,7 +450,7 @@ public class TimeLineController { MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(), ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning() : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning()); - LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS + logger.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS } if (markDBNotStale) { setEventsDBStale(false); @@ -453,16 +460,27 @@ public class TimeLineController { SwingUtilities.invokeLater(TimeLineController.this::showWindow); TimeLineController.this.showFullRange(); } else { - //prompt user to pick specific event and time range - ShowInTimelineDialog showInTimelineDilaog = - (file == null) - ? new ShowInTimelineDialog(TimeLineController.this, artifact) - : new ShowInTimelineDialog(TimeLineController.this, file); - Optional dialogResult = showInTimelineDilaog.showAndWait(); - dialogResult.ifPresent(viewInTimelineRequestedEvent -> { - SwingUtilities.invokeLater(TimeLineController.this::showWindow); - showInListView(viewInTimelineRequestedEvent); //show requested event in list view - }); + + try { + //prompt user to pick specific event and time range + ShowInTimelineDialog showInTimelineDilaog = (file == null) + ? new ShowInTimelineDialog(TimeLineController.this, artifact) + : new ShowInTimelineDialog(TimeLineController.this, file); + Optional dialogResult = showInTimelineDilaog.showAndWait(); + dialogResult.ifPresent(viewInTimelineRequestedEvent -> { + SwingUtilities.invokeLater(TimeLineController.this::showWindow); + try { + showInListView(viewInTimelineRequestedEvent); //show requested event in list view + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error showing requested events in listview: " + viewInTimelineRequestedEvent, ex); + new Alert(Alert.AlertType.ERROR, "There was an error opening Timeline.").showAndWait(); + } + }); + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error showing Timeline " , tskCoreException); + new Alert(Alert.AlertType.ERROR, "There was an error opening Timeline.").showAndWait(); + } + } break; case FAILED: @@ -493,8 +511,8 @@ public class TimeLineController { * Rebuild the entire repo in the background, and show the timeline when * done. * - * @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.JFX) @@ -506,8 +524,8 @@ public class TimeLineController { * Drop the tags table and rebuild it in the background, and show the * timeline when done. * - * @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.JFX) @@ -529,10 +547,10 @@ 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) { + private void showInListView(ViewInTimelineRequestedEvent requestEvent) throws TskCoreException { synchronized (filteredEvents) { setViewMode(ViewMode.LIST); selectEventIDs(requestEvent.getEventIDs()); @@ -563,8 +581,8 @@ public class TimeLineController { * 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) @@ -576,7 +594,14 @@ public class TimeLineController { Case.addPropertyChangeListener(caseListener); listeningToAutopsy = true; } - Platform.runLater(() -> promptForRebuild(file, artifact)); + Platform.runLater(() -> { + try { + promptForRebuild(file, artifact); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error prompting for timeline rebuild.", ex); + new Alert(Alert.AlertType.ERROR, "There was an error opening Timeline.").showAndWait(); + } + }); } /** @@ -585,12 +610,12 @@ public class TimeLineController { * confirms, rebuilds the database. Shows the timeline window when the * rebuild is done, or immediately if the rebuild is not confirmed. * - * @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.JFX) - private void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) { + private void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException { //if there is an existing prompt or progressdialog, just show that if (promptDialogManager.bringCurrentDialogToFront()) { return; @@ -626,7 +651,7 @@ public class TimeLineController { * The potential reasons are not necessarily orthogonal to each other. * * @return A list of reasons why the user might won't to rebuild the - * database. + * database. */ @ThreadConfined(type = ThreadConfined.ThreadType.ANY) @NbBundle.Messages({"TimeLineController.errorTitle=Timeline error.", @@ -635,7 +660,7 @@ public class TimeLineController { "TimeLineController.rebuildReasons.outOfDate=The event data is out of date: Not all events will be visible.", "TimeLineController.rebuildReasons.ingestWasRunning=The Timeline events database was previously populated while ingest was running: Some events may be missing, incomplete, or inaccurate.", "TimeLineController.rebuildReasons.incompleteOldSchema=The Timeline events database was previously populated without incomplete information: Some features may be unavailable or non-functional unless you update the events database."}) - private List getRebuildReasons() { + private List getRebuildReasons() throws TskCoreException { ArrayList rebuildReasons = new ArrayList<>(); try { @@ -645,7 +670,7 @@ public class TimeLineController { } } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Error determing the state of the timeline db. We will assume the it is out of date.", ex); //NON-NLS + logger.log(Level.SEVERE, "Error determing the state of the timeline db. We will assume the it is out of date.", ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.TimeLineController_errorTitle(), Bundle.TimeLineController_outOfDate_errorMessage()); rebuildReasons.add(Bundle.TimeLineController_rebuildReasons_outOfDateError()); @@ -666,7 +691,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) { synchronized (filteredEvents) { @@ -726,7 +751,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) { //clamp timerange to case @@ -821,7 +846,7 @@ public class TimeLineController { * * @param eventIDs The eventIDs to select */ - synchronized public void selectEventIDs(Collection eventIDs) { + synchronized public void selectEventIDs(Collection eventIDs) throws TskCoreException { selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs)); selectedEventIDs.setAll(eventIDs); } @@ -847,7 +872,7 @@ public class TimeLineController { } } catch (InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); //NON-NLS + logger.log(Level.SEVERE, getTitle() + " Unexpected error", ex); //NON-NLS } } }; @@ -915,7 +940,7 @@ public class TimeLineController { * Register the given object to receive events. * * @param o The object to register. Must implement public methods annotated - * with Subscribe. + * with Subscribe. */ synchronized public void registerForEvents(Object o) { eventbus.register(o); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java index e77f95952c..739fe9855b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java @@ -20,10 +20,14 @@ package org.sleuthkit.autopsy.timeline.actions; import java.awt.event.ActionEvent; import java.util.Set; +import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.AbstractAction; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.actions.SystemAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.timeline.ArtifactEventType; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -37,12 +41,13 @@ import org.sleuthkit.datamodel.timeline.EventType; public final class ViewArtifactInTimelineAction extends AbstractAction { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(ViewFileInTimelineAction.class.getName()); - private static final Set ARTIFACT_EVENT_TYPES = - EventType.allTypes.stream() - .filter((EventType t) -> t instanceof ArtifactEventType) - .map(ArtifactEventType.class::cast) - .collect(Collectors.toSet()); + private static final Set ARTIFACT_EVENT_TYPES + = EventType.allTypes.stream() + .filter((EventType t) -> t instanceof ArtifactEventType) + .map(ArtifactEventType.class::cast) + .collect(Collectors.toSet()); private final BlackboardArtifact artifact; @@ -54,7 +59,12 @@ public final class ViewArtifactInTimelineAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { - SystemAction.get(OpenTimelineAction.class).showArtifactInTimeline(artifact); + try { + SystemAction.get(OpenTimelineAction.class).showArtifactInTimeline(artifact); + } catch (TskCoreException ex) { + MessageNotifyUtil.Message.error("Error opening Timeline"); + logger.log(Level.SEVERE, "Error showing timeline.", ex); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java index 73c511a3be..e763f4c428 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java @@ -19,11 +19,15 @@ package org.sleuthkit.autopsy.timeline.actions; import java.awt.event.ActionEvent; +import java.util.logging.Level; import javax.swing.AbstractAction; import org.openide.util.NbBundle; import org.openide.util.actions.SystemAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; /** @@ -34,6 +38,8 @@ public final class ViewFileInTimelineAction extends AbstractAction { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(ViewFileInTimelineAction.class.getName()); + private final AbstractFile file; private ViewFileInTimelineAction(AbstractFile file, String displayName) { @@ -62,6 +68,11 @@ public final class ViewFileInTimelineAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { - SystemAction.get(OpenTimelineAction.class).showFileInTimeline(file); + try { + SystemAction.get(OpenTimelineAction.class).showFileInTimeline(file); + } catch (TskCoreException ex) { + MessageNotifyUtil.Message.error("Error opening Timeline"); + logger.log(Level.SEVERE, "Error showing timeline.", ex); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java index 340d9d8182..63849b84ba 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java @@ -18,19 +18,24 @@ */ package org.sleuthkit.autopsy.timeline.actions; +import java.util.logging.Level; import javafx.beans.binding.BooleanBinding; +import javafx.scene.control.Alert; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import org.controlsfx.control.action.Action; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.datamodel.TskCoreException; /** * */ public class ZoomToEvents extends Action { + private static final Logger logger = Logger.getLogger(ZoomToEvents.class.getName()); private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N NON-NLS @NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events", @@ -40,7 +45,12 @@ public class ZoomToEvents extends Action { setLongText(Bundle.ZoomToEvents_longText()); setGraphic(new ImageView(MAGNIFIER_OUT)); setEventHandler(actionEvent -> { - controller.zoomOutToActivity(); + try { + controller.zoomOutToActivity(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error invoking ZoomToEvents action", ex); + new Alert(Alert.AlertType.ERROR, "Error zomming").showAndWait(); + } }); //disable action when the current time range already encompases the entire case. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/Bundle.properties similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/Bundle.properties rename to Core/src/org/sleuthkit/autopsy/timeline/datamodel/Bundle.properties diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/Bundle_ja.properties new file mode 100644 index 0000000000..58fb6cff54 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/Bundle_ja.properties @@ -0,0 +1,29 @@ +msgdlg.problem.text=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u306b\u5165\u529b\u3059\u308b\u969b\u306b\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u5168\u3066\u306e\u30a4\u30d9\u30f3\u30c8\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u6b63\u78ba\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 +progressWindow.msg.commitingDb=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u30b3\u30df\u30c3\u30c8\u3057\u3066\u3044\u307e\u3059\u3002 +progressWindow.msg.gatheringData=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u3092\u53ce\u96c6\u4e2d +progressWindow.msg.populateMacEventsFiles=\u30d5\u30a1\u30a4\u30eb\u306eMAC\u30bf\u30a4\u30e0\u3092\u5165\u529b\u4e2d +progressWindow.msg.refreshingFileTags=\u30d5\u30a1\u30a4\u30eb\u30bf\u30b0\u3092\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5\u4e2d +progressWindow.msg.refreshingResultTags=\u7d50\u679c\u30bf\u30b0\u3092\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5\u4e2d +progressWindow.populatingXevents={0}\u30a4\u30d9\u30f3\u30c8\u3092\u5165\u529b\u4e2d +BaseTypes.fileSystem.name=\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0 +BaseTypes.miscTypes.name=\u305d\u306e\u4ed6\u30bf\u30a4\u30d7 +BaseTypes.webActivity.name=\u30a6\u30a7\u30d6\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3 +FileSystemTypes.fileAccessed.name=\u30a2\u30af\u30bb\u30b9\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +FileSystemTypes.fileChanged.name=\u5909\u66f4\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +FileSystemTypes.fileCreated.name=\u4f5c\u6210\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +FileSystemTypes.fileModified.name=\u4fee\u6b63\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +MiscTypes.Calls.name=\u30b3\u30fc\u30eb +MiscTypes.devicesAttached.name=\u63a5\u7d9a\u3055\u308c\u3066\u3044\u308b\u6a5f\u5668 +MiscTypes.Email.name=Email +MiscTypes.exif.name=Exif +MiscTypes.GPSRoutes.name=GPS\u30eb\u30fc\u30c8 +MiscTypes.GPSTrackpoint.name=\u4f4d\u7f6e\u60c5\u5831\u5c65\u6b74 +MiscTypes.installedPrograms.name=\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u308b\u30d7\u30ed\u30b0\u30e9\u30e0 +MiscTypes.message.name=\u30e1\u30c3\u30bb\u30fc\u30b8 +MiscTypes.recentDocuments.name=\u6700\u8fd1\u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8 +RootEventType.eventTypes.name=\u30a4\u30d9\u30f3\u30c8\u30bf\u30a4\u30d7 +WebTypes.webBookmarks.name=\u30a6\u30a7\u30d6\u30d6\u30c3\u30af\u30de\u30fc\u30af +WebTypes.webCookies.name=\u30a6\u30a7\u30d6\u30af\u30c3\u30ad\u30fc +WebTypes.webDownloads.name=\u30a6\u30a7\u30d6\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 +WebTypes.webHistory.name=\u30a6\u30a7\u30d6\u5c65\u6b74 +WebTypes.webSearch.name=\u30a6\u30a7\u30d6\u691c\u7d22 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventsRepository.java similarity index 82% rename from Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java rename to Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventsRepository.java index 2001fce4d1..9c8bbb5974 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventsRepository.java @@ -16,8 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.timeline.db; +package org.sleuthkit.autopsy.timeline.datamodel; +import com.google.common.base.Function; +import com.google.common.base.Supplier; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -59,16 +61,15 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.CancellationProgressTask; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.datamodel.timeline.ArtifactEventType; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.EventDB; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.timeline.CombinedEvent; @@ -103,12 +104,12 @@ public class EventsRepository { private final Executor workerExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("eventrepository-worker-%d").build()); //NON-NLS private DBPopulationWorker dbWorker; - private final EventDB eventDB; + private final TimelineManager eventManager; private final Case autoCase; private final FilteredEventsModel modelInstance; - private final LoadingCache maxCache; - private final LoadingCache minCache; + private final LoadingCache maxCache; + private final LoadingCache minCache; private final LoadingCache idToEventCache; private final LoadingCache> eventCountsCache; private final LoadingCache> eventStripeCache; @@ -133,37 +134,56 @@ public class EventsRepository { return hashSetMap; } - public Interval getBoundingEventsInterval(Interval timeRange, RootFilter filter, DateTimeZone tz) { - return eventDB.getBoundingEventsInterval(timeRange, filter,tz ); + public Interval getBoundingEventsInterval(Interval timeRange, RootFilter filter, DateTimeZone tz) throws TskCoreException { + return eventManager.getBoundingEventsInterval(timeRange, filter, tz); } /** * @return a FilteredEvetns object with this repository as underlying source - * of events + * of events */ public FilteredEventsModel getEventsModel() { return modelInstance; } - public EventsRepository(Case autoCase, ReadOnlyObjectProperty currentStateProperty) { + public EventsRepository(Case autoCase, ReadOnlyObjectProperty currentStateProperty) throws TskCoreException { this.autoCase = autoCase; - //TODO: we should check that case is open, or get passed a case object/directory -jm - this.eventDB = EventDB.getEventDB(autoCase.getSleuthkitCase()); + this.eventManager = autoCase.getSleuthkitCase().getTimelineManager(); populateFilterData(autoCase.getSleuthkitCase()); idToEventCache = CacheBuilder.newBuilder() .maximumSize(5000L) .expireAfterAccess(10, TimeUnit.MINUTES) - .build(CacheLoader.from(eventDB::getEventById)); + .build(new CacheLoader() { + @Override + public SingleEvent load(Long eventID) throws Exception { + return eventManager.getEventById(eventID); + } + }); eventCountsCache = CacheBuilder.newBuilder() .maximumSize(1000L) .expireAfterAccess(10, TimeUnit.MINUTES) - .build(CacheLoader.from(eventDB::countEventsByType)); + .build(CacheLoader.from(eventManager::countEventsByType)); eventStripeCache = CacheBuilder.newBuilder() .maximumSize(1000L) .expireAfterAccess(10, TimeUnit.MINUTES - ).build(CacheLoader.from((params) -> eventDB.getEventStripes(params,TimeLineController.getJodaTimeZone()))); - maxCache = CacheBuilder.newBuilder().build(CacheLoader.from(eventDB::getMaxTime)); - minCache = CacheBuilder.newBuilder().build(CacheLoader.from(eventDB::getMinTime)); + ).build(new CacheLoader>() { + @Override + public List load(ZoomParams params) throws Exception { + return eventManager.getEventStripes(params, TimeLineController.getJodaTimeZone()); + } + }); + maxCache = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public Long load(Void nil) throws TskCoreException { + return eventManager.getMaxTime(); + } + }); + minCache = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public Long load(Void nil) throws TskCoreException { + return eventManager.getMinTime(); + } + }); this.modelInstance = new FilteredEventsModel(this, currentStateProperty); } @@ -171,7 +191,7 @@ public class EventsRepository { * @return min time (in seconds from unix epoch) */ public Long getMaxTime() { - return maxCache.getUnchecked("max"); // NON-NLS + return maxCache.getUnchecked(null); // NON-NLS } @@ -179,7 +199,7 @@ public class EventsRepository { * @return max tie (in seconds from unix epoch) */ public Long getMinTime() { - return minCache.getUnchecked("min"); // NON-NLS + return minCache.getUnchecked(null); // NON-NLS } @@ -208,26 +228,23 @@ public class EventsRepository { } synchronized public int countAllEvents() { - return eventDB.countAllEvents(); + return eventManager.countAllEvents(); } /** * Get a List of event IDs for the events that are derived from the given * file. * - * @param file The AbstractFile to get derived event IDs - * for. + * @param file The AbstractFile to get derived event IDs for. * @param includeDerivedArtifacts If true, also get event IDs for events - * derived from artifacts derived form this - * file. If false, only gets events derived - * directly from this file (file system - * timestamps). + * derived from artifacts derived form this file. If false, only gets events + * derived directly from this file (file system timestamps). * * @return A List of event IDs for the events that are derived from the - * given file. + * given file. */ - public List getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) { - return eventDB.getEventIDsForFile(file, includeDerivedArtifacts); + public List getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) throws TskCoreException { + return eventManager.getEventIDsForFile(file, includeDerivedArtifacts); } /** @@ -237,10 +254,10 @@ public class EventsRepository { * @param artifact The BlackboardArtifact to get derived event IDs for. * * @return A List of event IDs for the events that are derived from the - * given artifact. + * given artifact. */ - public List getEventIDsForArtifact(BlackboardArtifact artifact) { - return eventDB.getEventIDsForArtifact(artifact); + public List getEventIDsForArtifact(BlackboardArtifact artifact) throws TskCoreException { + return eventManager.getEventIDsForArtifact(artifact); } private void invalidateCaches() { @@ -251,8 +268,8 @@ public class EventsRepository { idToEventCache.invalidateAll(); } - public List getEventIDs(Interval timeRange, RootFilter filter) { - return eventDB.getEventIDs(timeRange, filter); + public List getEventIDs(Interval timeRange, RootFilter filter) throws TskCoreException { + return eventManager.getEventIDs(timeRange, filter); } /** @@ -262,20 +279,20 @@ public class EventsRepository { * together. * * @param timeRange The Interval that all returned events must be within. - * @param filter The Filter that all returned events must pass. + * @param filter The Filter that all returned events must pass. * * @return A List of combined events, sorted by timestamp. */ - public List getCombinedEvents(Interval timeRange, RootFilter filter) { - return eventDB.getCombinedEvents(timeRange, filter); + public List getCombinedEvents(Interval timeRange, RootFilter filter) throws TskCoreException { + return eventManager.getCombinedEvents(timeRange, filter); } - public Interval getSpanningInterval(Collection eventIDs) { - return eventDB.getSpanningInterval(eventIDs); + public Interval getSpanningInterval(Collection eventIDs) throws TskCoreException { + return eventManager.getSpanningInterval(eventIDs); } - public boolean hasNewColumns() { - return eventDB.hasNewColumns(); + public boolean hasNewColumns() throws TskCoreException { + return eventManager.hasNewColumns(); } /** @@ -286,8 +303,8 @@ public class EventsRepository { * * @return a map from tagname displayname to count of applications */ - public Map getTagCountsByTagName(Set eventIDsWithTags) { - return eventDB.getTagCountsByTagName(eventIDsWithTags); + public Map getTagCountsByTagName(Set eventIDsWithTags) throws TskCoreException { + return eventManager.getTagCountsByTagName(eventIDsWithTags); } /** @@ -296,13 +313,13 @@ public class EventsRepository { * * @param skCase */ - synchronized private void populateFilterData(SleuthkitCase skCase) { + synchronized private void populateFilterData(SleuthkitCase skCase) throws TskCoreException { - for (Map.Entry hashSet : eventDB.getHashSetNames().entrySet()) { + for (Map.Entry hashSet : eventManager.getHashSetNames().entrySet()) { hashSetMap.putIfAbsent(hashSet.getKey(), hashSet.getValue()); } //because there is no way to remove a datasource we only add to this map. - for (Long id : eventDB.getDataSourceIDs()) { + for (Long id : eventManager.getDataSourceIDs()) { try { datasourcesMap.putIfAbsent(id, skCase.getContentById(id).getDataSource().getName()); } catch (TskCoreException ex) { @@ -318,16 +335,16 @@ public class EventsRepository { } } - synchronized public Set addTag(long objID, Long artifactID, Tag tag, EventDB.EventTransaction trans) { - Set updatedEventIDs = eventDB.addTag(objID, artifactID, tag, trans); + synchronized public Set addTag(long objID, Long artifactID, Tag tag) throws TskCoreException { + Set updatedEventIDs = eventManager.addTag(objID, artifactID, tag); if (!updatedEventIDs.isEmpty()) { invalidateCaches(updatedEventIDs); } return updatedEventIDs; } - synchronized public Set deleteTag(long objID, Long artifactID, long tagID, boolean tagged) { - Set updatedEventIDs = eventDB.deleteTag(objID, artifactID, tagID, tagged); + synchronized public Set deleteTag(long objID, Long artifactID, long tagID, boolean tagged) throws TskCoreException { + Set updatedEventIDs = eventManager.deleteTag(objID, artifactID, tagID, tagged); if (!updatedEventIDs.isEmpty()) { invalidateCaches(updatedEventIDs); } @@ -351,7 +368,7 @@ public class EventsRepository { * that don't have them. New filters are selected by default. * * @param tagsFilter the tags filter to modify so it is consistent with the - * tags in use in the case + * tags in use in the case */ public void syncTagsFilter(TagsFilter tagsFilter) { for (TagName t : tagNames) { @@ -363,7 +380,7 @@ public class EventsRepository { } public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) { - return eventDB.getSQLWhere(f1).equals(eventDB.getSQLWhere(f2)); + return eventManager.getSQLWhere(f1).equals(eventManager.getSQLWhere(f2)); } /** @@ -371,11 +388,11 @@ public class EventsRepository { * rebuild the entire repo. * * @param onStateChange called when he background task changes state. - * Clients can use this to handle failure, or cleanup - * operations for example. + * Clients can use this to handle failure, or cleanup operations for + * example. * * @return the task that will rebuild the repo in a background thread. The - * task has already been started. + * task has already been started. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public CancellationProgressTask rebuildRepository(Consumer onStateChange) { @@ -387,11 +404,11 @@ public class EventsRepository { * drop and rebuild the tags in the repo. * * @param onStateChange called when he background task changes state. - * Clients can use this to handle failure, or cleanup - * operations for example. + * Clients can use this to handle failure, or cleanup operations for + * example. * * @return the task that will rebuild the repo in a background thread. The - * task has already been started. + * task has already been started. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public CancellationProgressTask rebuildTags(Consumer onStateChange) { @@ -401,13 +418,13 @@ public class EventsRepository { /** * rebuild the repo. * - * @param mode the rebuild mode to use. + * @param mode the rebuild mode to use. * @param onStateChange called when he background task changes state. - * Clients can use this to handle failure, or cleanup - * operations for example. + * Clients can use this to handle failure, or cleanup operations for + * example. * * @return the task that will rebuild the repo in a background thread. The - * task has already been started. + * task has already been started. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private CancellationProgressTask rebuildRepository(final DBPopulationMode mode, Consumer onStateChange) { @@ -512,49 +529,47 @@ public class EventsRepository { "progressWindow.msg.gatheringData=Gathering event data", "progressWindow.msg.commitingDb=Committing events database"}) protected Void call() throws Exception { - EventDB.EventTransaction trans = null; - + SleuthkitCase.CaseDbTransaction trans = eventManager.beginTransaction(); if (dbPopulationMode == DBPopulationMode.FULL) { //drop old db, and add back MAC and artifact events logger.log(Level.INFO, "Beginning population of timeline db."); // NON-NLS restartProgressHandle(Bundle.progressWindow_msg_gatheringData(), "", -1D, 1, true); //reset database //TODO: can we do more incremental updates? -jm - eventDB.reInitializeDB(); + eventManager.reInitializeDB(); //grab ids of all files - List fileIDs = skCase.findAllFileIdsWhere("name != '.' AND name != '..'" + - " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()); //NON-NLS + List fileIDs = skCase.findAllFileIdsWhere("name != '.' AND name != '..'" + + " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()); //NON-NLS final int numFiles = fileIDs.size(); - trans = eventDB.beginTransaction(); - insertMACTimeEvents(numFiles, fileIDs, trans); - insertArtifactDerivedEvents(trans); + insertMACTimeEvents(numFiles, fileIDs); + insertArtifactDerivedEvents(); } //tags if (dbPopulationMode == DBPopulationMode.TAGS_ONLY) { - trans = eventDB.beginTransaction(); + trans = eventManager.beginTransaction(); logger.log(Level.INFO, "dropping old tags"); // NON-NLS - eventDB.reInitializeTags(); + eventManager.reInitializeTags(); } logger.log(Level.INFO, "updating content tags"); // NON-NLS List contentTags = tagsManager.getAllContentTags(); int currentWorkTotal = contentTags.size(); restartProgressHandle(Bundle.progressWindow_msg_refreshingFileTags(), "", 0D, currentWorkTotal, true); - insertContentTags(currentWorkTotal, contentTags, trans); + insertContentTags(currentWorkTotal, contentTags); logger.log(Level.INFO, "updating artifact tags"); // NON-NLS List artifactTags = tagsManager.getAllBlackboardArtifactTags(); currentWorkTotal = artifactTags.size(); restartProgressHandle(Bundle.progressWindow_msg_refreshingResultTags(), "", 0D, currentWorkTotal, true); - insertArtifactTags(currentWorkTotal, artifactTags, trans); + insertArtifactTags(currentWorkTotal, artifactTags); logger.log(Level.INFO, "committing db"); // NON-NLS Platform.runLater(() -> cancellable.set(false)); restartProgressHandle(Bundle.progressWindow_msg_commitingDb(), "", -1D, 1, false); - eventDB.commitTransaction(trans); + eventManager.commitTransaction(trans); - eventDB.analyze(); + eventManager.analyze(); populateFilterData(skCase); invalidateCaches(); @@ -565,29 +580,29 @@ public class EventsRepository { return null; } - private void insertArtifactTags(int currentWorkTotal, List artifactTags, EventDB.EventTransaction trans) { + private void insertArtifactTags(int currentWorkTotal, List artifactTags) throws TskCoreException { for (int i = 0; i < currentWorkTotal; i++) { if (isCancelRequested()) { break; } updateProgress(i, currentWorkTotal); BlackboardArtifactTag artifactTag = artifactTags.get(i); - eventDB.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag, trans); + eventManager.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag); } } - private void insertContentTags(int currentWorkTotal, List contentTags, EventDB.EventTransaction trans) { + private void insertContentTags(int currentWorkTotal, List contentTags) throws TskCoreException { for (int i = 0; i < currentWorkTotal; i++) { if (isCancelRequested()) { break; } updateProgress(i, currentWorkTotal); ContentTag contentTag = contentTags.get(i); - eventDB.addTag(contentTag.getContent().getId(), null, contentTag, trans); + eventManager.addTag(contentTag.getContent().getId(), null, contentTag); } } - private void insertArtifactDerivedEvents(EventDB.EventTransaction trans) { + private void insertArtifactDerivedEvents() { //insert artifact based events //TODO: use (not-yet existing api) to grab all artifacts with timestamps, rather than the hardcoded lists in EventType -jm for (EventType type : RootEventType.allTypes) { @@ -596,13 +611,13 @@ public class EventsRepository { } //skip file_system events, they are already handled above. if (type instanceof ArtifactEventType) { - populateEventType((ArtifactEventType) type, trans); + populateEventType((ArtifactEventType) type); } } } @NbBundle.Messages("progressWindow.msg.populateMacEventsFiles=Populating MAC time events for files") - private void insertMACTimeEvents(final int numFiles, List fileIDs, EventDB.EventTransaction trans) { + private void insertMACTimeEvents(final int numFiles, List fileIDs) { restartProgressHandle(Bundle.progressWindow_msg_populateMacEventsFiles(), "", 0D, numFiles, true); for (int i = 0; i < numFiles; i++) { if (isCancelRequested()) { @@ -615,7 +630,7 @@ public class EventsRepository { if (isNull(f)) { logger.log(Level.WARNING, "Failed to get data for file : {0}", fID); // NON-NLS } else { - insertEventsForFile(f, trans); + insertEventsForFile(f); updateProgress(i, numFiles); updateMessage(f.getName()); } @@ -625,7 +640,7 @@ public class EventsRepository { } } - private void insertEventsForFile(AbstractFile f, EventDB.EventTransaction trans) throws TskCoreException { + private void insertEventsForFile(AbstractFile f) throws TskCoreException { //gather time stamps into map EnumMap timeMap = new EnumMap<>(FileSystemTypes.class); timeMap.put(FileSystemTypes.FILE_CREATED, f.getCrtime()); @@ -657,9 +672,9 @@ public class EventsRepository { for (Map.Entry timeEntry : timeMap.entrySet()) { if (timeEntry.getValue() > 0) { // if the time is legitimate ( greater than zero ) insert it - eventDB.insertEvent(timeEntry.getValue(), timeEntry.getKey(), + eventManager.insertEvent(timeEntry.getValue(), timeEntry.getKey(), datasourceID, f.getId(), null, uniquePath, medDesc, - shortDesc, known, hashSets, tags, trans); + shortDesc, known, hashSets, tags); } } } @@ -685,10 +700,10 @@ public class EventsRepository { * populate all the events of one type * * @param type the type to populate - * @param trans the db transaction to use + * @param trans the db transaction to use */ @NbBundle.Messages({"# {0} - event type ", "progressWindow.populatingXevents=Populating {0} events"}) - private void populateEventType(final ArtifactEventType type, EventDB.EventTransaction trans) { + private void populateEventType(final ArtifactEventType type) { try { //get all the blackboard artifacts corresponding to the given event sub_type final ArrayList blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactTypeID()); @@ -697,7 +712,7 @@ public class EventsRepository { for (int i = 0; i < numArtifacts; i++) { try { //for each artifact, extract the relevant information for the descriptions - insertEventForArtifact(type, blackboardArtifacts.get(i), trans); + insertEventForArtifact(type, blackboardArtifacts.get(i)); updateProgress(i, numArtifacts); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "There was a problem inserting event for artifact: " + blackboardArtifacts.get(i).getArtifactID(), ex); // NON-NLS @@ -708,7 +723,7 @@ public class EventsRepository { } } - private void insertEventForArtifact(final ArtifactEventType type, BlackboardArtifact bbart, EventDB.EventTransaction trans) throws TskCoreException { + private void insertEventForArtifact(final ArtifactEventType type, BlackboardArtifact bbart) throws TskCoreException { ArtifactEventType.AttributeEventDescription eventDescription = ArtifactEventType.buildEventDescription(type, bbart); // if the time is legitimate ( greater than zero ) insert it into the db @@ -722,7 +737,7 @@ public class EventsRepository { String fullDescription = eventDescription.getFullDescription(); String medDescription = eventDescription.getMedDescription(); String shortDescription = eventDescription.getShortDescription(); - eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, objectID, artifactID, fullDescription, medDescription, shortDescription, null, hashSets, tags, trans); + eventManager.insertEvent(eventDescription.getTime(), type, datasourceID, objectID, artifactID, fullDescription, medDescription, shortDescription, null, hashSets, tags); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index d0deea604c..8c4912fc14 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -42,7 +42,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.events.AutopsyEvent; -import org.sleuthkit.autopsy.timeline.db.EventsRepository; import org.sleuthkit.autopsy.timeline.events.DBUpdatedEvent; import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent; @@ -260,7 +259,7 @@ public final class FilteredEventsModel { return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, Collections.emptySet()); } - public Interval getBoundingEventsInterval(DateTimeZone tz) { + public Interval getBoundingEventsInterval(DateTimeZone tz) throws TskCoreException { return repo.getBoundingEventsInterval(zoomParametersProperty().get().getTimeRange(), zoomParametersProperty().get().getFilter(), tz); } @@ -280,11 +279,11 @@ public final class FilteredEventsModel { * * @return a map from tagname displayname to count of applications */ - public Map getTagCountsByTagName(Set eventIDsWithTags) { + public Map getTagCountsByTagName(Set eventIDsWithTags) throws TskCoreException { return repo.getTagCountsByTagName(eventIDsWithTags); } - public List getEventIDs(Interval timeRange, Filter filter) { + public List getEventIDs(Interval timeRange, Filter filter) throws TskCoreException { final Interval overlap; final RootFilter intersect; synchronized (this) { @@ -303,7 +302,7 @@ public final class FilteredEventsModel { * * @return A List of combined events, sorted by timestamp. */ - public List getCombinedEvents() { + public List getCombinedEvents() throws TskCoreException { return repo.getCombinedEvents(requestedTimeRange.get(), requestedFilter.get()); } @@ -339,7 +338,7 @@ public final class FilteredEventsModel { /** * @return the smallest interval spanning all the given events */ - public Interval getSpanningInterval(Collection eventIDs) { + public Interval getSpanningInterval(Collection eventIDs) throws TskCoreException { return repo.getSpanningInterval(eventIDs); } @@ -391,17 +390,17 @@ public final class FilteredEventsModel { return repo.getEventStripes(params); } - synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) { + synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) throws TskCoreException { ContentTag contentTag = evt.getAddedTag(); Content content = contentTag.getContent(); - Set updatedEventIDs = repo.addTag(content.getId(), null, contentTag, null); + Set updatedEventIDs = repo.addTag(content.getId(), null, contentTag); return postTagsAdded(updatedEventIDs); } - synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) { + synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) throws TskCoreException { BlackboardArtifactTag artifactTag = evt.getAddedTag(); BlackboardArtifact artifact = artifactTag.getArtifact(); - Set updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag, null); + Set updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag); return postTagsAdded(updatedEventIDs); } @@ -446,7 +445,7 @@ public final class FilteredEventsModel { * @return A List of event IDs for the events that are derived from the * given file. */ - public List getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) { + public List getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) throws TskCoreException { return repo.getEventIDsForFile(file, includeDerivedArtifacts); } @@ -459,7 +458,7 @@ public final class FilteredEventsModel { * @return A List of event IDs for the events that are derived from the * given artifact. */ - public List getEventIDsForArtifact(BlackboardArtifact artifact) { + public List getEventIDsForArtifact(BlackboardArtifact artifact) throws TskCoreException { return repo.getEventIDsForArtifact(artifact); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/MultipleTransactionException.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/MultipleTransactionException.java similarity index 95% rename from Core/src/org/sleuthkit/autopsy/timeline/db/MultipleTransactionException.java rename to Core/src/org/sleuthkit/autopsy/timeline/datamodel/MultipleTransactionException.java index 197a920dea..24809b784b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/MultipleTransactionException.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/MultipleTransactionException.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.timeline.db; +package org.sleuthkit.autopsy.timeline.datamodel; /** * diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/Bundle_ja.properties deleted file mode 100644 index 841dbb761a..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/Bundle_ja.properties +++ /dev/null @@ -1,22 +0,0 @@ -BaseTypes.fileSystem.name=\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0 -BaseTypes.miscTypes.name=\u305D\u306E\u4ED6\u30BF\u30A4\u30D7 -BaseTypes.webActivity.name=\u30A6\u30A7\u30D6\u30A2\u30AF\u30C6\u30A3\u30D3\u30C6\u30A3 -FileSystemTypes.fileAccessed.name=\u30A2\u30AF\u30BB\u30B9\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB -FileSystemTypes.fileChanged.name=\u5909\u66F4\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB -FileSystemTypes.fileCreated.name=\u4F5C\u6210\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB -FileSystemTypes.fileModified.name=\u4FEE\u6B63\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB -MiscTypes.Calls.name=\u30B3\u30FC\u30EB -MiscTypes.devicesAttached.name=\u63A5\u7D9A\u3055\u308C\u3066\u3044\u308B\u6A5F\u5668 -MiscTypes.Email.name=Email -MiscTypes.exif.name=Exif -MiscTypes.GPSRoutes.name=GPS\u30EB\u30FC\u30C8 -MiscTypes.GPSTrackpoint.name=\u4F4D\u7F6E\u60C5\u5831\u5C65\u6B74 -MiscTypes.installedPrograms.name=\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u308B\u30D7\u30ED\u30B0\u30E9\u30E0 -MiscTypes.message.name=\u30E1\u30C3\u30BB\u30FC\u30B8 -MiscTypes.recentDocuments.name=\u6700\u8FD1\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8 -RootEventType.eventTypes.name=\u30A4\u30D9\u30F3\u30C8\u30BF\u30A4\u30D7 -WebTypes.webBookmarks.name=\u30A6\u30A7\u30D6\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF -WebTypes.webCookies.name=\u30A6\u30A7\u30D6\u30AF\u30C3\u30AD\u30FC -WebTypes.webDownloads.name=\u30A6\u30A7\u30D6\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9 -WebTypes.webHistory.name=\u30A6\u30A7\u30D6\u5C65\u6B74 -WebTypes.webSearch.name=\u30A6\u30A7\u30D6\u691C\u7D22 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/db/Bundle_ja.properties deleted file mode 100644 index b2bf6678a8..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/Bundle_ja.properties +++ /dev/null @@ -1,7 +0,0 @@ -msgdlg.problem.text=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u306B\u5165\u529B\u3059\u308B\u969B\u306B\u554F\u984C\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u5168\u3066\u306E\u30A4\u30D9\u30F3\u30C8\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u6B63\u78BA\u3067\u306F\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 -progressWindow.msg.commitingDb=\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u30A4\u30D9\u30F3\u30C8\u3092\u30B3\u30DF\u30C3\u30C8\u3057\u3066\u3044\u307E\u3059\u3002 -progressWindow.msg.gatheringData=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u3092\u53CE\u96C6\u4E2D -progressWindow.msg.populateMacEventsFiles=\u30D5\u30A1\u30A4\u30EB\u306EMAC\u30BF\u30A4\u30E0\u3092\u5165\u529B\u4E2D -progressWindow.msg.refreshingFileTags=\u30D5\u30A1\u30A4\u30EB\u30BF\u30B0\u3092\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u4E2D -progressWindow.msg.refreshingResultTags=\u7D50\u679C\u30BF\u30B0\u3092\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u4E2D -progressWindow.populatingXevents={0}\u30A4\u30D9\u30F3\u30C8\u3092\u5165\u529B\u4E2D diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 3e28c754ad..1060a5bb54 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; +import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -48,6 +49,7 @@ import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.action.Action; import org.joda.time.DateTime; import org.joda.time.Interval; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -57,6 +59,7 @@ import org.sleuthkit.autopsy.timeline.ViewMode; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart; import org.sleuthkit.autopsy.timeline.utils.MappedList; +import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.timeline.DescriptionLoD; import org.sleuthkit.datamodel.timeline.EventStripe; import org.sleuthkit.datamodel.timeline.TimeLineEvent; @@ -78,7 +81,7 @@ import org.sleuthkit.datamodel.timeline.ZoomParams; */ public class DetailViewPane extends AbstractTimelineChart, DetailsChart> { - private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName()); + private final static Logger logger = Logger.getLogger(DetailViewPane.class.getName()); private final DateAxis detailsChartDateAxis = new DateAxis(); private final DateAxis pinnedDateAxis = new DateAxis(); @@ -101,7 +104,7 @@ public class DetailViewPane extends AbstractTimelineChart detailNode.getEventIDs().stream()) - .collect(Collectors.toList())); + try { + //update controllers list of selected event ids when view's selection changes. + getController().selectEventIDs(getSelectedNodes().stream() + .flatMap(detailNode -> detailNode.getEventIDs().stream()) + .collect(Collectors.toList())); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error selecting nodes.", ex); + new Alert(Alert.AlertType.ERROR, "Error selecting nodes").showAndWait(); + } }); } @@ -147,7 +155,7 @@ public class DetailViewPane extends AbstractTimelineChart highlightedEvents) { highlightedEvents.addListener((Observable observable) -> { @@ -155,16 +163,16 @@ public class DetailViewPane extends AbstractTimelineChart> highlightPredicate = - highlightedEvents.stream() // => events - .map(TimeLineEvent::getDescription)// => event descriptions - .map(new Function>>() { - @Override - public Predicate> apply(String description) { - return eventNode -> StringUtils.equalsIgnoreCase(eventNode.getDescription(), description); - } - })// => predicates that match strings agains the descriptions of the events in highlightedEvents - .reduce(getSelectedNodes()::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes + Predicate> highlightPredicate + = highlightedEvents.stream() // => events + .map(TimeLineEvent::getDescription)// => event descriptions + .map(new Function>>() { + @Override + public Predicate> apply(String description) { + return eventNode -> StringUtils.equalsIgnoreCase(eventNode.getDescription(), description); + } + })// => predicates that match strings agains the descriptions of the events in highlightedEvents + .reduce(getSelectedNodes()::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes getChart().setHighlightPredicate(highlightPredicate); //use this predicate to highlight nodes }); } @@ -177,7 +185,7 @@ public class DetailViewPane extends AbstractTimelineChart selectedEventListener = new ListChangeListener() { @Override public void onChanged(ListChangeListener.Change c) { - controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream() - .filter(Objects::nonNull) - .map(CombinedEvent::getRepresentativeEventID) - .collect(Collectors.toSet())); + try { + controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream() + .filter(Objects::nonNull) + .map(CombinedEvent::getRepresentativeEventID) + .collect(Collectors.toSet())); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error selecting events.", ex); + new Alert(Alert.AlertType.ERROR, "error selecting events.").showAndWait(); + } } }; @@ -236,19 +243,19 @@ class ListTimeline extends BorderPane { //// set up cell and cell-value factories for columns dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY); - dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent -> - TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis()))); + dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent + -> TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis()))); descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY); - descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent -> - singleEvent.getDescription(DescriptionLoD.FULL))); + descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent + -> singleEvent.getDescription(DescriptionLoD.FULL))); typeColumn.setCellValueFactory(CELL_VALUE_FACTORY); typeColumn.setCellFactory(col -> new EventTypeCell()); knownColumn.setCellValueFactory(CELL_VALUE_FACTORY); - knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent -> - singleEvent.getKnown().getName())); + knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent + -> singleEvent.getKnown().getName())); taggedColumn.setCellValueFactory(CELL_VALUE_FACTORY); taggedColumn.setCellFactory(col -> new TaggedCell()); @@ -331,7 +338,7 @@ class ListTimeline extends BorderPane { * ListViewPane will provide to its host ViewFrame. * * @return A List of time navigation controls in the from of JavaFX scene - * graph Nodes. + * graph Nodes. */ List getTimeNavigationControls() { return Collections.singletonList(navControls); @@ -342,7 +349,7 @@ class ListTimeline extends BorderPane { * focus it. * * @param index The index of the item that should be scrolled in to view and - * focused. + * focused. */ private void scrollToAndFocus(Integer index) { table.requestFocus(); @@ -558,7 +565,7 @@ class ListTimeline extends BorderPane { * Constructor * * @param textSupplier Function that takes a SingleEvent and produces a - * String to show in this TableCell. + * String to show in this TableCell. */ TextEventTableCell(Function textSupplier) { this.textSupplier = textSupplier; @@ -730,14 +737,14 @@ class ListTimeline extends BorderPane { ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem(); ZoneId timeZoneID = TimeLineController.getTimeZoneID(); TemporalUnit selectedUnit = selectedChronoField.getBaseUnit(); - + int focusedIndex = table.getFocusModel().getFocusedIndex(); CombinedEvent focusedItem = table.getFocusModel().getFocusedItem(); if (-1 == focusedIndex || null == focusedItem) { focusedItem = visibleEvents.first(); focusedIndex = table.getItems().indexOf(focusedItem); } - + ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID); ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);// for (ChronoField field : SCROLL_BY_UNITS) { @@ -746,7 +753,7 @@ class ListTimeline extends BorderPane { } } long nextMillis = nextDateTime.toInstant().toEpochMilli(); - + int nextIndex = table.getItems().size() - 1; for (int i = focusedIndex; i < table.getItems().size(); i++) { if (table.getItems().get(i).getStartMillis() >= nextMillis) { @@ -774,24 +781,24 @@ class ListTimeline extends BorderPane { ZoneId timeZoneID = TimeLineController.getTimeZoneID(); ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem(); TemporalUnit selectedUnit = selectedChronoField.getBaseUnit(); - + int focusedIndex = table.getFocusModel().getFocusedIndex(); CombinedEvent focusedItem = table.getFocusModel().getFocusedItem(); if (-1 == focusedIndex || null == focusedItem) { focusedItem = visibleEvents.last(); focusedIndex = table.getItems().indexOf(focusedItem); } - + ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID); ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);// - + for (ChronoField field : SCROLL_BY_UNITS) { if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) { previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());// } } long previousMillis = previousDateTime.toInstant().toEpochMilli(); - + int previousIndex = 0; for (int i = focusedIndex; i > 0; i--) { if (table.getItems().get(i).getStartMillis() <= previousMillis) { @@ -799,7 +806,7 @@ class ListTimeline extends BorderPane { break; } } - + scrollToAndFocus(previousIndex); } });