mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge branch '6985-MiniTimelineDiscovery' of https://github.com/wschaeferB/autopsy into 7067-AddContextMenusToArtifacts
This commit is contained in:
commit
b1f047461b
@ -6,6 +6,7 @@ jobs:
|
||||
- os: linux
|
||||
dist: bionic
|
||||
- os: osx
|
||||
osx_image: xcode12.2
|
||||
|
||||
env:
|
||||
global:
|
||||
|
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.contentviewers.contextviewer.ContextViewer.DateTimePanel;
|
||||
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
|
||||
@ -29,27 +30,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
|
||||
* usage, if known.
|
||||
*
|
||||
*/
|
||||
public final class ContextSourcePanel extends javax.swing.JPanel {
|
||||
public final class ContextSourcePanel extends javax.swing.JPanel implements DateTimePanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// defines a list of artifacts that provide context for a file
|
||||
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
|
||||
|
||||
static {
|
||||
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
|
||||
}
|
||||
|
||||
private final BlackboardArtifact sourceContextArtifact;
|
||||
|
||||
private final Long dateTime;
|
||||
|
||||
/**
|
||||
* Creates new form ContextViewer
|
||||
*/
|
||||
public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) {
|
||||
public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
|
||||
|
||||
initComponents();
|
||||
sourceContextArtifact = associatedArtifact;
|
||||
setSourceName(sourceName);
|
||||
setSourceText(sourceText);
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,10 +150,10 @@ public final class ContextSourcePanel extends javax.swing.JPanel {
|
||||
private void showSourceText(boolean show) {
|
||||
jSourceTextLabel.setVisible(show);
|
||||
}
|
||||
|
||||
|
||||
private void showSourceButton(boolean show) {
|
||||
jSourceGoToResultButton.setVisible(show);
|
||||
jSourceGoToResultButton.setEnabled(show);
|
||||
jSourceGoToResultButton.setEnabled(show);
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
|
@ -29,27 +29,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
|
||||
* usage, if known.
|
||||
*
|
||||
*/
|
||||
public final class ContextUsagePanel extends javax.swing.JPanel {
|
||||
public final class ContextUsagePanel extends javax.swing.JPanel implements ContextViewer.DateTimePanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// defines a list of artifacts that provide context for a file
|
||||
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
|
||||
|
||||
static {
|
||||
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
|
||||
}
|
||||
|
||||
private final BlackboardArtifact sourceContextArtifact;
|
||||
|
||||
private final Long dateTime;
|
||||
|
||||
/**
|
||||
* Creates new form ContextViewer
|
||||
*/
|
||||
public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) {
|
||||
public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
|
||||
|
||||
initComponents();
|
||||
sourceContextArtifact = associatedArtifact;
|
||||
setUsageName(sourceName);
|
||||
setUsageText(sourceText);
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,10 +147,10 @@ public final class ContextUsagePanel extends javax.swing.JPanel {
|
||||
private void showUsageText(boolean show) {
|
||||
jUsageTextLabel.setVisible(show);
|
||||
}
|
||||
|
||||
|
||||
private void showUsageButton(boolean show) {
|
||||
jUsageGoToResultButton.setVisible(show);
|
||||
jUsageGoToResultButton.setEnabled(show);
|
||||
jUsageGoToResultButton.setEnabled(show);
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
|
@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -56,8 +58,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
|
||||
// defines a list of artifacts that provide context for a file
|
||||
private static final List<BlackboardArtifact.ARTIFACT_TYPE> CONTEXT_ARTIFACTS = new ArrayList<>();
|
||||
private final List<javax.swing.JPanel> contextSourcePanels = new ArrayList<>();
|
||||
private final List<javax.swing.JPanel> contextUsagePanels = new ArrayList<>();
|
||||
private final List<ContextSourcePanel> contextSourcePanels = new ArrayList<>();
|
||||
private final List<ContextUsagePanel> contextUsagePanels = new ArrayList<>();
|
||||
|
||||
static {
|
||||
CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
|
||||
@ -338,32 +340,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
"ContextViewer.programExecution=Program Execution: "
|
||||
})
|
||||
private void addArtifactToPanels(BlackboardArtifact associatedArtifact) throws TskCoreException {
|
||||
Long dateTime = getArtifactDateTime(associatedArtifact);
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_attachmentSource();
|
||||
String sourceText = msgArtifactToAbbreviatedString(associatedArtifact);
|
||||
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextSourcePanels.add(sourcePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID()
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_downloadSource();
|
||||
String sourceText = webDownloadArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextSourcePanels.add(sourcePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_recentDocs();
|
||||
String sourceText = recentDocArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextUsagePanels.add(usagePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_programExecution();
|
||||
String sourceText = programExecArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextUsagePanels.add(usagePanel);
|
||||
}
|
||||
|
||||
Collections.sort(contextSourcePanels, new SortByDateTime());
|
||||
Collections.sort(contextUsagePanels, new SortByDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -532,6 +538,59 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
|
||||
return attributeMap;
|
||||
}
|
||||
|
||||
interface DateTimePanel {
|
||||
/**
|
||||
* Return the date time value for this panel.
|
||||
*
|
||||
* @return Date time value or null of one is not available.
|
||||
*/
|
||||
Long getDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dateTime value for the given message artifact.
|
||||
*
|
||||
* @param artifact
|
||||
*
|
||||
* @return Long dateTime value or null if the attribute was not found.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private Long getArtifactDateTime(BlackboardArtifact artifact) throws TskCoreException {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME));
|
||||
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == artifact.getArtifactTypeID()) {
|
||||
attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT));
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifact.getArtifactTypeID()
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == artifact.getArtifactTypeID()) {
|
||||
attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED));
|
||||
}
|
||||
return (attribute != null ? attribute.getValueLong() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for sorting lists of DateTimePanels.
|
||||
*/
|
||||
class SortByDateTime implements Comparator<DateTimePanel> {
|
||||
|
||||
@Override
|
||||
public int compare(DateTimePanel panel1, DateTimePanel panel2) {
|
||||
Long dateTime1 = panel1.getDateTime();
|
||||
Long dateTime2 = panel2.getDateTime();
|
||||
|
||||
if(dateTime1 == null && dateTime2 == null) {
|
||||
return 0;
|
||||
} else if(dateTime1 == null) {
|
||||
return -1;
|
||||
} else if(dateTime2 == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return dateTime1.compareTo(dateTime2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
|
@ -130,7 +130,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return new RecentFileDetails(path, lastOpened);
|
||||
return new RecentFileDetails(artifact, path, lastOpened);
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return new RecentDownloadDetails(path, accessedTime, domain);
|
||||
return new RecentDownloadDetails(artifact, path, accessedTime, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +298,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
if (date == null || date == 0 || StringUtils.isBlank(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new RecentAttachmentDetails(path, date, sender);
|
||||
return new RecentAttachmentDetails(messageArtifact, path, date, sender);
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,14 +324,17 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
|
||||
private final String path;
|
||||
private final long date;
|
||||
private final BlackboardArtifact artifact;
|
||||
|
||||
/**
|
||||
* Constructor for files with just a path and date.
|
||||
*
|
||||
* @param artifact The relevant artifact.
|
||||
* @param path File path.
|
||||
* @param date File access date\time in seconds with java epoch
|
||||
*/
|
||||
RecentFileDetails(String path, long date) {
|
||||
RecentFileDetails(BlackboardArtifact artifact, String path, long date) {
|
||||
this.artifact = artifact;
|
||||
this.path = path;
|
||||
this.date = date;
|
||||
}
|
||||
@ -364,6 +367,12 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The pertinent artifact for this recent file hit.
|
||||
*/
|
||||
public BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,12 +385,13 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
/**
|
||||
* Constructor for files with just a path and date.
|
||||
*
|
||||
* @param artifact The relevant artifact.
|
||||
* @param path File path.
|
||||
* @param date File access date\time in seconds with java epoch.
|
||||
* @param webDomain The webdomain from which the file was downloaded.
|
||||
*/
|
||||
RecentDownloadDetails(String path, long date, String webDomain) {
|
||||
super(path, date);
|
||||
RecentDownloadDetails(BlackboardArtifact artifact, String path, long date, String webDomain) {
|
||||
super(artifact, path, date);
|
||||
this.webDomain = webDomain;
|
||||
}
|
||||
|
||||
@ -407,13 +417,14 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
* Constructor for recent download files which have a path, date and
|
||||
* domain value.
|
||||
*
|
||||
* @param artifact The relevant artifact.
|
||||
* @param path File path.
|
||||
* @param date File crtime.
|
||||
* @param sender The sender of the message from which the file was
|
||||
* attached.
|
||||
*/
|
||||
RecentAttachmentDetails(String path, long date, String sender) {
|
||||
super(path, date);
|
||||
RecentAttachmentDetails(BlackboardArtifact artifact, String path, long date, String sender) {
|
||||
super(artifact, path, date);
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.datasourcesummary.datamodel;
|
||||
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineModule;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TimelineFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.DataSourceFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Utilities for interacting with Timeline in relation to data sources.
|
||||
*/
|
||||
public class TimelineDataSourceUtils {
|
||||
|
||||
private static TimelineDataSourceUtils instance = null;
|
||||
|
||||
/**
|
||||
* @return Singleton instance of this class.
|
||||
*/
|
||||
public static TimelineDataSourceUtils getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new TimelineDataSourceUtils();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor. Should be instantiated through getInstance().
|
||||
*/
|
||||
private TimelineDataSourceUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a RootFilter based on the default filter state but only the
|
||||
* specified dataSource is selected.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @return The root filter representing a default filter with only this data
|
||||
* source selected.
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public RootFilter getDataSourceFilter(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
|
||||
RootFilterState filterState = getDataSourceFilterState(dataSource);
|
||||
return filterState == null ? null : filterState.getActiveFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a TimeLineController based on the default filter state but only
|
||||
* the specified dataSource is selected.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @return The root filter state representing a default filter with only
|
||||
* this data source selected.
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public RootFilterState getDataSourceFilterState(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
|
||||
TimeLineController controller = TimeLineModule.getController();
|
||||
RootFilterState dataSourceState = controller.getEventsModel().getDefaultEventFilterState().copyOf();
|
||||
|
||||
for (FilterState<? extends TimelineFilter.DataSourceFilter> filterState : dataSourceState.getDataSourcesFilterState().getSubFilterStates()) {
|
||||
DataSourceFilter dsFilter = filterState.getFilter();
|
||||
if (dsFilter != null) {
|
||||
filterState.setSelected(dsFilter.getDataSourceID() == dataSource.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dataSourceState;
|
||||
}
|
||||
}
|
@ -38,12 +38,11 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TimelineEvent;
|
||||
import org.sleuthkit.datamodel.TimelineEventType;
|
||||
import org.sleuthkit.datamodel.TimelineFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.DataSourcesFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
|
||||
import org.sleuthkit.datamodel.TimelineManager;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import java.util.function.Supplier;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
|
||||
/**
|
||||
@ -51,6 +50,23 @@ import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
*/
|
||||
public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
|
||||
/**
|
||||
* A function for obtaining a Timeline RootFilter filtered to the specific
|
||||
* data source.
|
||||
*/
|
||||
public interface DataSourceFilterFunction {
|
||||
|
||||
/**
|
||||
* Obtains a Timeline RootFilter filtered to the specific data source.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @return The timeline root filter.
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
RootFilter apply(DataSource dataSource) throws NoCurrentCaseException, TskCoreException;
|
||||
}
|
||||
|
||||
private static final long DAY_SECS = 24 * 60 * 60;
|
||||
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = new HashSet<>(
|
||||
Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED));
|
||||
@ -64,23 +80,29 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
|
||||
private final SleuthkitCaseProvider caseProvider;
|
||||
private final Supplier<TimeZone> timeZoneProvider;
|
||||
private final DataSourceFilterFunction filterFunction;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public TimelineSummary() {
|
||||
this(SleuthkitCaseProvider.DEFAULT, () -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()));
|
||||
this(SleuthkitCaseProvider.DEFAULT,
|
||||
() -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()),
|
||||
(ds) -> TimelineDataSourceUtils.getInstance().getDataSourceFilter(ds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct object with given SleuthkitCaseProvider
|
||||
*
|
||||
* @param caseProvider SleuthkitCaseProvider provider, cannot be null.
|
||||
* @param timeZoneProvider The timezone provider, cannot be null.
|
||||
* @param caseProvider SleuthkitCaseProvider provider; cannot be null.
|
||||
* @param timeZoneProvider The timezone provider; cannot be null.
|
||||
* @param filterFunction Provides the default root filter function filtered
|
||||
* to the data source; cannot be null.
|
||||
*/
|
||||
public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier<TimeZone> timeZoneProvider) {
|
||||
public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier<TimeZone> timeZoneProvider, DataSourceFilterFunction filterFunction) {
|
||||
this.caseProvider = caseProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.filterFunction = filterFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -113,8 +135,9 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
* @return The retrieved data.
|
||||
* @throws SleuthkitCaseProviderException
|
||||
* @throws TskCoreException
|
||||
* @throws NoCurrentCaseException
|
||||
*/
|
||||
public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException {
|
||||
public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException, NoCurrentCaseException {
|
||||
TimeZone timeZone = this.timeZoneProvider.get();
|
||||
TimelineManager timelineManager = this.caseProvider.get().getTimelineManager();
|
||||
|
||||
@ -144,7 +167,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
// get most recent days activity
|
||||
List<DailyActivityAmount> mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay);
|
||||
|
||||
return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt);
|
||||
return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt, dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,25 +204,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
* belongs.
|
||||
* @return A Map mapping days from epoch to the activity for that day.
|
||||
* @throws TskCoreException
|
||||
* @throws NoCurrentCaseException
|
||||
*/
|
||||
private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone) throws TskCoreException {
|
||||
|
||||
DataSourcesFilter dataSourceFilter = new DataSourcesFilter();
|
||||
dataSourceFilter.addSubFilter(new TimelineFilter.DataSourceFilter(dataSource.getName(), dataSource.getId()));
|
||||
|
||||
RootFilter dataSourceRootFilter = new RootFilter(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
dataSourceFilter,
|
||||
null,
|
||||
Collections.emptySet());
|
||||
private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone)
|
||||
throws TskCoreException, NoCurrentCaseException {
|
||||
RootFilter rootFilter = this.filterFunction.apply(dataSource);
|
||||
|
||||
// get events for data source
|
||||
long curRunTime = System.currentTimeMillis();
|
||||
List<TimelineEvent> events = timelineManager.getEvents(new Interval(1, curRunTime), dataSourceRootFilter);
|
||||
List<TimelineEvent> events = timelineManager.getEvents(new Interval(1, curRunTime), rootFilter);
|
||||
|
||||
// get counts of events per day (left is file system events, right is everything else)
|
||||
Map<Long, DailyActivityAmount> dateCounts = new HashMap<>();
|
||||
@ -233,6 +246,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
private final Date minDate;
|
||||
private final Date maxDate;
|
||||
private final List<DailyActivityAmount> histogramActivity;
|
||||
private final DataSource dataSource;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
@ -240,12 +254,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
* @param minDate Earliest usage date recorded for the data source.
|
||||
* @param maxDate Latest usage date recorded for the data source.
|
||||
* @param recentDaysActivity A list of activity prior to and including
|
||||
* the latest usage date by day.
|
||||
* max date sorted by min to max date.
|
||||
* @param dataSource The data source for which this data applies. the
|
||||
* latest usage date by day.
|
||||
*/
|
||||
TimelineSummaryData(Date minDate, Date maxDate, List<DailyActivityAmount> recentDaysActivity) {
|
||||
TimelineSummaryData(Date minDate, Date maxDate, List<DailyActivityAmount> recentDaysActivity, DataSource dataSource) {
|
||||
this.minDate = minDate;
|
||||
this.maxDate = maxDate;
|
||||
this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity);
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,11 +281,18 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
|
||||
/**
|
||||
* @return A list of activity prior to and including the latest usage
|
||||
* date by day.
|
||||
* date by day sorted min to max date.
|
||||
*/
|
||||
public List<DailyActivityAmount> getMostRecentDaysActivity() {
|
||||
return histogramActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The data source that this data applies to.
|
||||
*/
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,8 +108,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT";
|
||||
private static final String WINDOWS_PREFIX = "/WINDOWS";
|
||||
|
||||
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess());
|
||||
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed());
|
||||
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccessed().compareTo(b.getLastAccessed());
|
||||
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccessed().compareTo(b.getLastAccessed());
|
||||
|
||||
/**
|
||||
* Sorts TopProgramsResults pushing highest run time count then most recent
|
||||
@ -126,8 +126,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
// second priority for sorting is the last run date
|
||||
// if non-0, this is the return value for the comparator
|
||||
int lastRunCompare = nullableCompare(
|
||||
a.getLastRun() == null ? null : a.getLastRun().getTime(),
|
||||
b.getLastRun() == null ? null : b.getLastRun().getTime());
|
||||
a.getLastAccessed() == null ? null : a.getLastAccessed().getTime(),
|
||||
b.getLastAccessed() == null ? null : b.getLastAccessed().getTime());
|
||||
|
||||
if (lastRunCompare != 0) {
|
||||
return -lastRunCompare;
|
||||
@ -219,14 +219,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Pair<Long, Map<String, List<Long>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
|
||||
Pair<Long, Map<String, List<Pair<BlackboardArtifact, Long>>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
|
||||
// if no recent domains, return accordingly
|
||||
if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final long mostRecentMs = mostRecentAndGroups.getLeft();
|
||||
Map<String, List<Long>> groups = mostRecentAndGroups.getRight();
|
||||
Map<String, List<Pair<BlackboardArtifact, Long>>> groups = mostRecentAndGroups.getRight();
|
||||
|
||||
return groups.entrySet().stream()
|
||||
.map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs))
|
||||
@ -243,24 +243,32 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* within DOMAIN_WINDOW_MS of mostRecentMs.
|
||||
*
|
||||
* @param domain The domain.
|
||||
* @param visits The number of visits.
|
||||
* @param visits The list of the artifact and its associated time in
|
||||
* milliseconds.
|
||||
* @param mostRecentMs The most recent visit of any domain.
|
||||
*
|
||||
* @return The TopDomainsResult or null if no visits to this domain within
|
||||
* 30 days of mostRecentMs.
|
||||
*/
|
||||
private TopDomainsResult getDomainsResult(String domain, List<Long> visits, long mostRecentMs) {
|
||||
private TopDomainsResult getDomainsResult(String domain, List<Pair<BlackboardArtifact, Long>> visits, long mostRecentMs) {
|
||||
long visitCount = 0;
|
||||
Long thisMostRecentMs = null;
|
||||
BlackboardArtifact thisMostRecentArtifact = null;
|
||||
|
||||
for (Long visitMs : visits) {
|
||||
for (Pair<BlackboardArtifact, Long> visitInstance : visits) {
|
||||
BlackboardArtifact artifact = visitInstance.getLeft();
|
||||
Long visitMs = visitInstance.getRight();
|
||||
// make sure that visit is within window of mostRecentMS; otherwise skip it.
|
||||
if (visitMs + DOMAIN_WINDOW_MS < mostRecentMs) {
|
||||
if (visitMs == null || visitMs + DOMAIN_WINDOW_MS < mostRecentMs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if visit is within window, increment the count and get most recent
|
||||
visitCount++;
|
||||
if (thisMostRecentMs == null || visitMs > thisMostRecentMs) {
|
||||
thisMostRecentMs = visitMs;
|
||||
thisMostRecentArtifact = artifact;
|
||||
}
|
||||
thisMostRecentMs = getMax(thisMostRecentMs, visitMs);
|
||||
}
|
||||
|
||||
@ -269,7 +277,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return null;
|
||||
} else {
|
||||
// create a top domain result with the domain, count, and most recent visit date
|
||||
return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs));
|
||||
return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs), thisMostRecentArtifact);
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,17 +289,18 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
*
|
||||
* @return A tuple where the first value is the latest web history accessed
|
||||
* date in milliseconds and the second value maps normalized (lowercase;
|
||||
* trimmed) domain names to when those domains were visited.
|
||||
* trimmed) domain names to when those domains were visited and the relevant
|
||||
* artifact.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws SleuthkitCaseProviderException
|
||||
*/
|
||||
private Pair<Long, Map<String, List<Long>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException {
|
||||
private Pair<Long, Map<String, List<Pair<BlackboardArtifact, Long>>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException {
|
||||
List<BlackboardArtifact> artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY,
|
||||
dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0);
|
||||
|
||||
Long mostRecentMs = null;
|
||||
Map<String, List<Long>> domainVisits = new HashMap<>();
|
||||
Map<String, List<Pair<BlackboardArtifact, Long>>> domainVisits = new HashMap<>();
|
||||
|
||||
for (BlackboardArtifact art : artifacts) {
|
||||
Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED);
|
||||
@ -312,13 +321,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
domain = domain.toLowerCase().trim();
|
||||
|
||||
// add this visit date to the list of dates for the domain
|
||||
List<Long> domainVisitList = domainVisits.get(domain);
|
||||
List<Pair<BlackboardArtifact, Long>> domainVisitList = domainVisits.get(domain);
|
||||
if (domainVisitList == null) {
|
||||
domainVisitList = new ArrayList<>();
|
||||
domainVisits.put(domain, domainVisitList);
|
||||
}
|
||||
|
||||
domainVisitList.add(artifactDateMs);
|
||||
domainVisitList.add(Pair.of(art, artifactDateMs));
|
||||
}
|
||||
|
||||
return Pair.of(mostRecentMs, domainVisits);
|
||||
@ -354,7 +363,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT);
|
||||
Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED);
|
||||
return (StringUtils.isNotBlank(searchString) && dateAccessed != null)
|
||||
? new TopWebSearchResult(searchString, dateAccessed)
|
||||
? new TopWebSearchResult(searchString, dateAccessed, artifact)
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -456,15 +465,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @return The most recent one with a non-null date.
|
||||
*/
|
||||
private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) {
|
||||
if (r2.getDateAccessed() == null) {
|
||||
if (r2.getLastAccessed()== null) {
|
||||
return r1;
|
||||
}
|
||||
|
||||
if (r1.getDateAccessed() == null) {
|
||||
if (r1.getLastAccessed() == null) {
|
||||
return r2;
|
||||
}
|
||||
|
||||
return r1.getDateAccessed().compareTo(r2.getDateAccessed()) >= 0 ? r1 : r2;
|
||||
return r1.getLastAccessed().compareTo(r2.getLastAccessed()) >= 0 ? r1 : r2;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -495,7 +504,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_ID),
|
||||
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE),
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL)
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL),
|
||||
artifact
|
||||
);
|
||||
})
|
||||
// remove Root Hub identifier
|
||||
@ -524,7 +534,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE);
|
||||
Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME);
|
||||
return (StringUtils.isNotBlank(type) && date != null)
|
||||
? new TopAccountResult(type, date)
|
||||
? new TopAccountResult(type, date, artifact)
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -552,7 +562,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
}
|
||||
|
||||
return (StringUtils.isNotBlank(type) && latestDate != null)
|
||||
? new TopAccountResult(type, latestDate)
|
||||
? new TopAccountResult(type, latestDate, artifact)
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -690,7 +700,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
programName,
|
||||
path,
|
||||
longCount,
|
||||
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME)
|
||||
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
|
||||
artifact
|
||||
);
|
||||
}
|
||||
|
||||
@ -786,11 +797,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
res.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()),
|
||||
res -> res,
|
||||
(res1, res2) -> {
|
||||
Long maxRunTimes = getMax(res1.getRunTimes(), res2.getRunTimes());
|
||||
Date maxDate = getMax(res1.getLastAccessed(), res2.getLastAccessed());
|
||||
TopProgramsResult maxResult = TOP_PROGRAMS_RESULT_COMPARE.compare(res1, res2) >= 0 ? res1 : res2;
|
||||
return new TopProgramsResult(
|
||||
res1.getProgramName(),
|
||||
res1.getProgramPath(),
|
||||
getMax(res1.getRunTimes(), res2.getRunTimes()),
|
||||
getMax(res1.getLastRun(), res2.getLastRun()));
|
||||
maxResult.getProgramName(),
|
||||
maxResult.getProgramPath(),
|
||||
maxRunTimes,
|
||||
maxDate,
|
||||
maxResult.getArtifact());
|
||||
})).values();
|
||||
|
||||
List<TopProgramsResult> orderedResults = results.stream()
|
||||
@ -803,7 +818,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
// if run times / last run information is available, the first item should have some value,
|
||||
// and then the items should be limited accordingly.
|
||||
if (isPositiveNum(topResult.getRunTimes())
|
||||
|| (topResult.getLastRun() != null && isPositiveNum(topResult.getLastRun().getTime()))) {
|
||||
|| (topResult.getLastAccessed() != null && isPositiveNum(topResult.getLastAccessed().getTime()))) {
|
||||
return orderedResults.stream().limit(count).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -812,13 +827,47 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return orderedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class including date of last access and the relevant blackboard
|
||||
* artifact.
|
||||
*/
|
||||
public static class LastAccessedArtifact {
|
||||
|
||||
private final Date lastAccessed;
|
||||
private final BlackboardArtifact artifact;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param lastAccessed The date of last access.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public LastAccessedArtifact(Date lastAccessed, BlackboardArtifact artifact) {
|
||||
this.lastAccessed = lastAccessed;
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date of last access.
|
||||
*/
|
||||
public Date getLastAccessed() {
|
||||
return lastAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The associated artifact.
|
||||
*/
|
||||
public BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object containing information about a web search artifact.
|
||||
*/
|
||||
public static class TopWebSearchResult {
|
||||
public static class TopWebSearchResult extends LastAccessedArtifact {
|
||||
|
||||
private final String searchString;
|
||||
private final Date dateAccessed;
|
||||
private String translatedResult;
|
||||
|
||||
/**
|
||||
@ -826,10 +875,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
*
|
||||
* @param searchString The search string.
|
||||
* @param dateAccessed The latest date searched.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public TopWebSearchResult(String searchString, Date dateAccessed) {
|
||||
public TopWebSearchResult(String searchString, Date dateAccessed, BlackboardArtifact artifact) {
|
||||
super(dateAccessed, artifact);
|
||||
this.searchString = searchString;
|
||||
this.dateAccessed = dateAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -854,22 +904,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public String getSearchString() {
|
||||
return searchString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date for the search.
|
||||
*/
|
||||
public Date getDateAccessed() {
|
||||
return dateAccessed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A record of a device attached.
|
||||
*/
|
||||
public static class TopDeviceAttachedResult {
|
||||
public static class TopDeviceAttachedResult extends LastAccessedArtifact {
|
||||
|
||||
private final String deviceId;
|
||||
private final Date dateAccessed;
|
||||
private final String deviceMake;
|
||||
private final String deviceModel;
|
||||
|
||||
@ -880,10 +922,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @param dateAccessed The date last attached.
|
||||
* @param deviceMake The device make.
|
||||
* @param deviceModel The device model.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) {
|
||||
public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel, BlackboardArtifact artifact) {
|
||||
super(dateAccessed, artifact);
|
||||
this.deviceId = deviceId;
|
||||
this.dateAccessed = dateAccessed;
|
||||
this.deviceMake = deviceMake;
|
||||
this.deviceModel = deviceModel;
|
||||
}
|
||||
@ -895,13 +938,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date last attached.
|
||||
*/
|
||||
public Date getDateAccessed() {
|
||||
return dateAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The device make.
|
||||
*/
|
||||
@ -921,20 +957,20 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* A record of an account and the last time it was used determined by
|
||||
* messages.
|
||||
*/
|
||||
public static class TopAccountResult {
|
||||
public static class TopAccountResult extends LastAccessedArtifact {
|
||||
|
||||
private final String accountType;
|
||||
private final Date lastAccess;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param accountType The account type.
|
||||
* @param lastAccess The date the account was last accessed.
|
||||
* @param artifact The artifact indicating last access.
|
||||
*/
|
||||
public TopAccountResult(String accountType, Date lastAccess) {
|
||||
public TopAccountResult(String accountType, Date lastAccess, BlackboardArtifact artifact) {
|
||||
super(lastAccess, artifact);
|
||||
this.accountType = accountType;
|
||||
this.lastAccess = lastAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -943,23 +979,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public String getAccountType() {
|
||||
return accountType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date the account was last accessed.
|
||||
*/
|
||||
public Date getLastAccess() {
|
||||
return lastAccess;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a result of a program run on a datasource.
|
||||
*/
|
||||
public static class TopDomainsResult {
|
||||
public static class TopDomainsResult extends LastAccessedArtifact {
|
||||
|
||||
private final String domain;
|
||||
private final Long visitTimes;
|
||||
private final Date lastVisit;
|
||||
|
||||
/**
|
||||
* Describes a top domain result.
|
||||
@ -967,11 +995,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @param domain The domain.
|
||||
* @param visitTimes The number of times it was visited.
|
||||
* @param lastVisit The date of the last visit.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) {
|
||||
public TopDomainsResult(String domain, Long visitTimes, Date lastVisit, BlackboardArtifact artifact) {
|
||||
super(lastVisit, artifact);
|
||||
this.domain = domain;
|
||||
this.visitTimes = visitTimes;
|
||||
this.lastVisit = lastVisit;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -987,24 +1016,16 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public Long getVisitTimes() {
|
||||
return visitTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date of the last visit.
|
||||
*/
|
||||
public Date getLastVisit() {
|
||||
return lastVisit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a result of a program run on a datasource.
|
||||
*/
|
||||
public static class TopProgramsResult {
|
||||
public static class TopProgramsResult extends LastAccessedArtifact {
|
||||
|
||||
private final String programName;
|
||||
private final String programPath;
|
||||
private final Long runTimes;
|
||||
private final Date lastRun;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
@ -1012,12 +1033,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @param programName The name of the program.
|
||||
* @param programPath The path of the program.
|
||||
* @param runTimes The number of runs.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
|
||||
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun, BlackboardArtifact artifact) {
|
||||
super(lastRun, artifact);
|
||||
this.programName = programName;
|
||||
this.programPath = programPath;
|
||||
this.runTimes = runTimes;
|
||||
this.lastRun = lastRun;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1040,12 +1062,5 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public Long getRunTimes() {
|
||||
return runTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The last time the program was run or null if not present.
|
||||
*/
|
||||
public Date getLastRun() {
|
||||
return lastRun;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,20 +19,29 @@
|
||||
package org.sleuthkit.autopsy.datasourcesummary.ui;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
|
||||
@ -41,6 +50,8 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor;
|
||||
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
|
||||
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
@ -53,6 +64,8 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
/**
|
||||
* Base class from which other tabs in data source summary derive.
|
||||
*/
|
||||
@Messages({"BaseDataSourceSummaryPanel_goToArtifact=View Source Result",
|
||||
"BaseDataSourceSummaryPanel_goToFile=View Source File in Directory"})
|
||||
abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -64,6 +77,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
private final EventUpdateHandler updateHandler;
|
||||
private final List<UpdateGovernor> governors;
|
||||
|
||||
private Runnable notifyParentClose = null;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
@ -77,7 +92,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* Checks to see if artifact is from a datasource.
|
||||
*
|
||||
* @param art The artifact.
|
||||
* @param ds The datasource.
|
||||
* @param ds The datasource.
|
||||
*
|
||||
* @return True if in datasource; false if not or exception.
|
||||
*/
|
||||
@ -219,7 +234,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* Main constructor.
|
||||
*
|
||||
* @param governors The items governing when this panel should receive
|
||||
* updates.
|
||||
* updates.
|
||||
*/
|
||||
protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) {
|
||||
this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors);
|
||||
@ -227,6 +242,104 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
this.updateHandler.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the relevant artifact, navigates to the artifact in the tree and
|
||||
* closes data source summary dialog if open.
|
||||
*
|
||||
* @param artifact The artifact.
|
||||
* @return The menu item for a go to artifact menu item.
|
||||
*/
|
||||
protected CellModelTableCellRenderer.MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
|
||||
if (artifact == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CellModelTableCellRenderer.DefaultMenuItem(
|
||||
Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
|
||||
() -> {
|
||||
final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
|
||||
|
||||
// Navigate to the source context artifact.
|
||||
if (dtc != null && artifact != null) {
|
||||
dtc.viewArtifact(artifact);
|
||||
}
|
||||
|
||||
notifyParentClose();
|
||||
});
|
||||
}
|
||||
|
||||
private static final Pattern windowsDrivePattern = Pattern.compile("^[A-Za-z]\\:(.*)$");
|
||||
|
||||
/**
|
||||
* Normalizes the path for lookup in the sleuthkit database (unix endings;
|
||||
* remove C:\).
|
||||
*
|
||||
* @param path The path to normalize.
|
||||
* @return The normalized path.
|
||||
*/
|
||||
private String normalizePath(String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmed = path.trim();
|
||||
Matcher match = windowsDrivePattern.matcher(trimmed);
|
||||
if (match.find()) {
|
||||
return FilenameUtils.normalize(match.group(1), true);
|
||||
} else {
|
||||
return FilenameUtils.normalize(trimmed, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a menu item to navigate to a file.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return The menu item or null if file cannot be found in data source.
|
||||
*/
|
||||
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(String path) {
|
||||
if (StringUtils.isNotBlank(path)) {
|
||||
Path p = Paths.get(path);
|
||||
String fileName = normalizePath(p.getFileName().toString());
|
||||
String directory = normalizePath(p.getParent().toString());
|
||||
|
||||
if (fileName != null && directory != null) {
|
||||
try {
|
||||
List<AbstractFile> files = Case.getCurrentCaseThrows().getSleuthkitCase().findFiles(getDataSource(), fileName, directory);
|
||||
if (CollectionUtils.isNotEmpty(files)) {
|
||||
return getFileNavigateItem(files.get(0));
|
||||
}
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "There was an error fetching file for path: " + path, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the relevant artifact, navigates to the artifact's content in the
|
||||
* tree and closes data source summary dialog if open.
|
||||
*
|
||||
* @param artifact The artifact.
|
||||
* @return The menu item list for a go to artifact menu item.
|
||||
*/
|
||||
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(AbstractFile file) {
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CellModelTableCellRenderer.DefaultMenuItem(
|
||||
Bundle.BaseDataSourceSummaryPanel_goToFile(),
|
||||
() -> {
|
||||
new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
|
||||
.actionPerformed(null);
|
||||
|
||||
notifyParentClose();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes listeners and resources.
|
||||
*/
|
||||
@ -246,6 +359,24 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
onNewDataSource(this.dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event that parent should close.
|
||||
*/
|
||||
protected void notifyParentClose() {
|
||||
if (notifyParentClose != null) {
|
||||
notifyParentClose.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener for parent close events.
|
||||
*
|
||||
* @param action The action to run when parent is to close.
|
||||
*/
|
||||
void setParentCloseListener(Runnable action) {
|
||||
notifyParentClose = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current data source.
|
||||
*/
|
||||
@ -287,7 +418,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* argument and data fetch components and then submits them to run.
|
||||
*
|
||||
* @param dataFetchComponents The components to be run.
|
||||
* @param dataSource The data source argument.
|
||||
* @param dataSource The data source argument.
|
||||
*/
|
||||
protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
|
||||
if (dataSource == null || !Case.isCaseOpen()) {
|
||||
@ -320,8 +451,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* argument and submits them to be executed.
|
||||
*
|
||||
* @param dataFetchComponents The components to register.
|
||||
* @param loadableComponents The components to set to a loading screen.
|
||||
* @param dataSource The data source argument.
|
||||
* @param loadableComponents The components to set to a loading screen.
|
||||
* @param dataSource The data source argument.
|
||||
*/
|
||||
protected void onNewDataSource(
|
||||
List<DataFetchComponents<DataSource, ?>> dataFetchComponents,
|
||||
@ -371,12 +502,11 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* message indicating the unrun ingest module will be shown. Otherwise, the
|
||||
* default LoadableComponent.showDataFetchResult behavior will be used.
|
||||
*
|
||||
* @param component The component.
|
||||
* @param result The data result.
|
||||
* @param component The component.
|
||||
* @param result The data result.
|
||||
* @param factoryClass The fully qualified class name of the relevant
|
||||
* factory.
|
||||
* @param moduleName The name of the ingest module (i.e. 'Keyword
|
||||
* Search').
|
||||
* factory.
|
||||
* @param moduleName The name of the ingest module (i.e. 'Keyword Search').
|
||||
*/
|
||||
protected <T> void showResultWithModuleCheck(LoadableComponent<List<T>> component, DataFetchResult<List<T>> result, String factoryClass, String moduleName) {
|
||||
Predicate<List<T>> hasResults = (lst) -> lst != null && !lst.isEmpty();
|
||||
@ -389,14 +519,13 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* message indicating the unrun ingest module will be shown. Otherwise, the
|
||||
* default LoadableComponent.showDataFetchResult behavior will be used.
|
||||
*
|
||||
* @param component The component.
|
||||
* @param result The data result.
|
||||
* @param hasResults Given the data type, will provide whether or not the
|
||||
* data contains any actual results.
|
||||
* @param component The component.
|
||||
* @param result The data result.
|
||||
* @param hasResults Given the data type, will provide whether or not the
|
||||
* data contains any actual results.
|
||||
* @param factoryClass The fully qualified class name of the relevant
|
||||
* factory.
|
||||
* @param moduleName The name of the ingest module (i.e. 'Keyword
|
||||
* Search').
|
||||
* factory.
|
||||
* @param moduleName The name of the ingest module (i.e. 'Keyword Search').
|
||||
*/
|
||||
protected <T> void showResultWithModuleCheck(LoadableComponent<T> component, DataFetchResult<T> result,
|
||||
Predicate<T> hasResults, String factoryClass, String moduleName) {
|
||||
|
@ -43,3 +43,12 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as
|
||||
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
|
||||
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
|
||||
TimelinePanel.activityRangeLabel.text=Activity Range
|
||||
RecentFilesPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
|
||||
TimelinePanel.viewInTimelineBtn.text=View in Timeline
|
||||
|
@ -3,6 +3,8 @@ AnalysisPanel_keyColumn_title=Name
|
||||
AnalysisPanel_keywordSearchModuleName=Keyword Search
|
||||
# {0} - module name
|
||||
BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source.
|
||||
BaseDataSourceSummaryPanel_goToArtifact=View Source Result
|
||||
BaseDataSourceSummaryPanel_goToFile=View Source File in Directory
|
||||
ContainerPanel_setFieldsForNonImageDataSource_na=N/A
|
||||
CTL_DataSourceSummaryAction=Data Source Summary
|
||||
DataSourceSummaryDialog.closeButton.text=Close
|
||||
@ -103,6 +105,15 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as
|
||||
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
|
||||
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
|
||||
TimelinePanel.activityRangeLabel.text=Activity Range
|
||||
RecentFilesPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
|
||||
TimelinePanel.viewInTimelineBtn.text=View in Timeline
|
||||
UserActivityPanel_noDataExists=No communication data exists
|
||||
UserActivityPanel_tab_title=User Activity
|
||||
UserActivityPanel_TopAccountTableModel_accountType_header=Account Type
|
||||
|
@ -61,6 +61,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
|
||||
Map<Long, Long> fileCountsMap = CaseDataSourcesSummary.getCountsOfFiles();
|
||||
dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap);
|
||||
dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane();
|
||||
dataSourceSummaryTabbedPane.setParentCloseListener(() -> DataSourceSummaryDialog.this.dispose());
|
||||
initComponents();
|
||||
dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel);
|
||||
dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> {
|
||||
@ -169,7 +170,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
|
||||
* source matches the dataSourceID it will select the first datasource.
|
||||
*
|
||||
* @param dataSourceID the ID of the datasource to select, null will cause
|
||||
* the first datasource to be selected
|
||||
* the first datasource to be selected
|
||||
*/
|
||||
void selectDataSource(Long dataSourceId) {
|
||||
dataSourcesPanel.selectDataSource(dataSourceId);
|
||||
|
@ -47,7 +47,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
* Records of tab information (i.e. title, component, function to call on
|
||||
* new data source).
|
||||
*/
|
||||
private static class DataSourceTab {
|
||||
private class DataSourceTab {
|
||||
|
||||
private final String tabTitle;
|
||||
private final Component component;
|
||||
@ -74,8 +74,12 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
*
|
||||
* @param tabTitle The title of the tab.
|
||||
* @param panel The component to be displayed in the tab.
|
||||
* @param notifyParentClose Notifies parent to trigger a close.
|
||||
*/
|
||||
DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
|
||||
|
||||
panel.setParentCloseListener(() -> notifyParentClose());
|
||||
|
||||
this.tabTitle = tabTitle;
|
||||
this.component = panel;
|
||||
this.onDataSource = panel::setDataSource;
|
||||
@ -116,6 +120,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
private static final String TABBED_PANE = "tabbedPane";
|
||||
private static final String NO_DATASOURCE_PANE = "noDataSourcePane";
|
||||
|
||||
private Runnable notifyParentClose = null;
|
||||
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
|
||||
|
||||
private final List<DataSourceTab> tabs = Arrays.asList(
|
||||
@ -142,6 +147,24 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
postInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event that parent should close.
|
||||
*/
|
||||
private void notifyParentClose() {
|
||||
if (notifyParentClose != null) {
|
||||
notifyParentClose.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener for parent close events.
|
||||
*
|
||||
* @param parentCloseAction The observer.
|
||||
*/
|
||||
void setParentCloseListener(Runnable parentCloseAction) {
|
||||
notifyParentClose = parentCloseAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called right after initComponents during initialization.
|
||||
*/
|
||||
|
@ -94,7 +94,7 @@
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
@ -106,7 +106,7 @@
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="6" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="8" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
@ -140,7 +140,7 @@
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
@ -156,7 +156,55 @@
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="7" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="6" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="9" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
|
@ -21,13 +21,16 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
|
||||
@ -77,6 +80,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
initalizeTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a base class of RecentFileDetails and provides the pertinent menu
|
||||
* items.
|
||||
*
|
||||
* @param record The RecentFileDetails instance.
|
||||
* @return The menu items list containing one action or navigating to the
|
||||
* appropriate artifact/file and closing the data source summary dialog if
|
||||
* open.
|
||||
*/
|
||||
private Supplier<List<MenuItem>> getPopupFunct(RecentFileDetails record) {
|
||||
return () -> {
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<MenuItem> toRet = new ArrayList<>();
|
||||
|
||||
MenuItem fileNav = getFileNavigateItem(record.getPath());
|
||||
if (fileNav != null) {
|
||||
toRet.add(fileNav);
|
||||
}
|
||||
|
||||
if (record.getArtifact() != null) {
|
||||
toRet.add(getArtifactNavigateItem(record.getArtifact()));
|
||||
}
|
||||
|
||||
return (toRet.size() > 0) ? toRet : null;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fetchInformation(DataSource dataSource) {
|
||||
fetchInformation(dataFetchComponents, dataSource);
|
||||
@ -113,11 +146,13 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
List<ColumnModel<RecentFileDetails>> list = Arrays.asList(
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getPath());
|
||||
return new DefaultCellModel(prog.getPath())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 250),
|
||||
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getDateAsString());
|
||||
return new DefaultCellModel(prog.getDateAsString())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 80));
|
||||
|
||||
ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list);
|
||||
@ -126,6 +161,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
pane.setModel(tableModel);
|
||||
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
|
||||
pane.setKeyFunction((recentFile) -> recentFile.getPath());
|
||||
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
tablePanelList.add(pane);
|
||||
|
||||
DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker
|
||||
@ -148,15 +184,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList(
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getWebDomain());
|
||||
return new DefaultCellModel(prog.getWebDomain())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 100),
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getPath());
|
||||
return new DefaultCellModel(prog.getPath())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 250),
|
||||
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getDateAsString());
|
||||
return new DefaultCellModel(prog.getDateAsString())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 80));
|
||||
|
||||
ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list);
|
||||
@ -165,6 +204,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
pane.setModel(tableModel);
|
||||
pane.setKeyFunction((download) -> download.getPath());
|
||||
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
|
||||
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
tablePanelList.add(pane);
|
||||
|
||||
DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker
|
||||
@ -187,15 +227,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList(
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getPath());
|
||||
return new DefaultCellModel(prog.getPath())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 250),
|
||||
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getDateAsString());
|
||||
return new DefaultCellModel(prog.getDateAsString())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 80),
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getSender());
|
||||
return new DefaultCellModel(prog.getSender())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 150));
|
||||
|
||||
ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list);
|
||||
@ -204,6 +247,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
pane.setModel(tableModel);
|
||||
pane.setKeyFunction((attachment) -> attachment.getPath());
|
||||
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
|
||||
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
tablePanelList.add(pane);
|
||||
|
||||
DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker
|
||||
@ -234,6 +278,9 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
javax.swing.JLabel openDocsLabel = new javax.swing.JLabel();
|
||||
javax.swing.JLabel downloadLabel = new javax.swing.JLabel();
|
||||
javax.swing.JLabel attachmentLabel = new javax.swing.JLabel();
|
||||
javax.swing.JLabel rightClickForMoreOptions1 = new javax.swing.JLabel();
|
||||
javax.swing.JLabel rightClickForMoreOptions2 = new javax.swing.JLabel();
|
||||
javax.swing.JLabel rightClickForMoreOptions3 = new javax.swing.JLabel();
|
||||
|
||||
setLayout(new java.awt.BorderLayout());
|
||||
|
||||
@ -263,7 +310,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
tablePanel.add(openedDocPane, gridBagConstraints);
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 4;
|
||||
gridBagConstraints.gridy = 5;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
@ -272,7 +319,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
tablePanel.add(downloadsPane, gridBagConstraints);
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 6;
|
||||
gridBagConstraints.gridy = 8;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
@ -291,7 +338,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
org.openide.awt.Mnemonics.setLocalizedText(downloadLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.downloadLabel.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 3;
|
||||
gridBagConstraints.gridy = 4;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
|
||||
tablePanel.add(downloadLabel, gridBagConstraints);
|
||||
@ -299,12 +346,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
org.openide.awt.Mnemonics.setLocalizedText(attachmentLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.attachmentLabel.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 5;
|
||||
gridBagConstraints.gridy = 7;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
|
||||
tablePanel.add(attachmentLabel, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions1, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions1.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 3;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
tablePanel.add(rightClickForMoreOptions1, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions2, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions2.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 6;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
tablePanel.add(rightClickForMoreOptions2, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions3, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions3.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 9;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
tablePanel.add(rightClickForMoreOptions3, gridBagConstraints);
|
||||
|
||||
scrollPane.setViewportView(tablePanel);
|
||||
|
||||
add(scrollPane, java.awt.BorderLayout.CENTER);
|
||||
|
@ -171,7 +171,7 @@
|
||||
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalStrut"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="sameIdPanel">
|
||||
<Container class="javax.swing.JPanel" name="last30DaysPanel">
|
||||
<Properties>
|
||||
<Property name="alignmentX" type="float" value="0.0"/>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -193,6 +193,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="viewInTimelineBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="TimelinePanel.viewInTimelineBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="viewInTimelineBtnActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler5">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
|
@ -26,8 +26,15 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
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;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData;
|
||||
@ -41,7 +48,11 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetch
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
|
||||
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineModule;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A tab shown in data source summary displaying information about a data
|
||||
@ -54,7 +65,8 @@ import org.sleuthkit.datamodel.DataSource;
|
||||
"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");
|
||||
private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d");
|
||||
@ -75,13 +87,14 @@ 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 TimelineDataSourceUtils timelineUtils = TimelineDataSourceUtils.getInstance();
|
||||
|
||||
// all loadable components on this tab
|
||||
private final List<LoadableComponent<?>> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart);
|
||||
|
||||
// actions to load data for this tab
|
||||
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
|
||||
|
||||
|
||||
public TimelinePanel() {
|
||||
this(new TimelineSummary());
|
||||
}
|
||||
@ -96,7 +109,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
(dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT),
|
||||
(result) -> handleResult(result))
|
||||
);
|
||||
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
@ -112,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);
|
||||
|
||||
@ -132,24 +145,27 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
// Create a bar chart item for each recent days activity item
|
||||
List<BarChartItem> fileEvtCounts = new ArrayList<>();
|
||||
List<BarChartItem> 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;
|
||||
|
||||
/**
|
||||
* Handles displaying the result for each displayable item in the
|
||||
@ -163,18 +179,99 @@ 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);
|
||||
}
|
||||
} else {
|
||||
synchronized (this.timelineBtnLock) {
|
||||
this.viewInTimelineBtn.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that occurs when 'View in Timeline' button is pressed.
|
||||
*/
|
||||
private void openFilteredChart() {
|
||||
DataSource dataSource = null;
|
||||
Date minDate = null;
|
||||
Date maxDate = null;
|
||||
|
||||
// get date from current timelineData if that data exists.
|
||||
synchronized (this.timelineBtnLock) {
|
||||
if (curTimelineData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource = curTimelineData.getDataSource();
|
||||
if (CollectionUtils.isNotEmpty(curTimelineData.getMostRecentDaysActivity())) {
|
||||
minDate = curTimelineData.getMostRecentDaysActivity().get(0).getDay();
|
||||
maxDate = curTimelineData.getMostRecentDaysActivity().get(curTimelineData.getMostRecentDaysActivity().size() - 1).getDay();
|
||||
// set outer bound to end of day instead of beginning
|
||||
if (maxDate != null) {
|
||||
maxDate = new Date(maxDate.getTime() + 1000 * 60 * 60 * 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Interval timeSpan = null;
|
||||
|
||||
try {
|
||||
final TimeLineController controller = TimeLineModule.getController();
|
||||
|
||||
if (dataSource != null) {
|
||||
controller.pushFilters(timelineUtils.getDataSourceFilterState(dataSource));
|
||||
}
|
||||
|
||||
if (minDate != null && maxDate != null) {
|
||||
timeSpan = new Interval(new DateTime(minDate), new DateTime(maxDate));
|
||||
}
|
||||
} catch (NoCurrentCaseException | TskCoreException 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();
|
||||
@ -198,7 +295,8 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
javax.swing.JPanel earliestLabelPanel = earliestLabel;
|
||||
javax.swing.JPanel latestLabelPanel = latestLabel;
|
||||
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
|
||||
javax.swing.JPanel sameIdPanel = last30DaysChart;
|
||||
javax.swing.JPanel last30DaysPanel = last30DaysChart;
|
||||
viewInTimelineBtn = new javax.swing.JButton();
|
||||
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
|
||||
|
||||
mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
@ -233,12 +331,21 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
filler2.setAlignmentX(0.0F);
|
||||
mainContentPanel.add(filler2);
|
||||
|
||||
sameIdPanel.setAlignmentX(0.0F);
|
||||
sameIdPanel.setMaximumSize(new java.awt.Dimension(600, 300));
|
||||
sameIdPanel.setMinimumSize(new java.awt.Dimension(600, 300));
|
||||
sameIdPanel.setPreferredSize(new java.awt.Dimension(600, 300));
|
||||
sameIdPanel.setVerifyInputWhenFocusTarget(false);
|
||||
mainContentPanel.add(sameIdPanel);
|
||||
last30DaysPanel.setAlignmentX(0.0F);
|
||||
last30DaysPanel.setMaximumSize(new java.awt.Dimension(600, 300));
|
||||
last30DaysPanel.setMinimumSize(new java.awt.Dimension(600, 300));
|
||||
last30DaysPanel.setPreferredSize(new java.awt.Dimension(600, 300));
|
||||
last30DaysPanel.setVerifyInputWhenFocusTarget(false);
|
||||
mainContentPanel.add(last30DaysPanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(viewInTimelineBtn, org.openide.util.NbBundle.getMessage(TimelinePanel.class, "TimelinePanel.viewInTimelineBtn.text")); // NOI18N
|
||||
viewInTimelineBtn.setEnabled(false);
|
||||
viewInTimelineBtn.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
viewInTimelineBtnActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
mainContentPanel.add(viewInTimelineBtn);
|
||||
|
||||
filler5.setAlignmentX(0.0F);
|
||||
mainContentPanel.add(filler5);
|
||||
@ -257,7 +364,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void viewInTimelineBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInTimelineBtnActionPerformed
|
||||
openFilteredChart();
|
||||
}//GEN-LAST:event_viewInTimelineBtnActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton viewInTimelineBtn;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -135,6 +135,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler3">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -204,6 +215,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler4">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -273,6 +295,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler6">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -342,6 +375,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions4">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions4.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler8">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -411,6 +455,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions5">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions5.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
@ -29,12 +29,14 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.LastAccessedArtifact;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
|
||||
@ -92,7 +94,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
Bundle.UserActivityPanel_TopProgramsTableModel_name_header(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getProgramName())
|
||||
.setTooltip(prog.getProgramPath());
|
||||
.setTooltip(prog.getProgramPath())
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
250),
|
||||
// program folder column
|
||||
@ -103,7 +106,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
getShortFolderName(
|
||||
prog.getProgramPath(),
|
||||
prog.getProgramName()))
|
||||
.setTooltip(prog.getProgramPath());
|
||||
.setTooltip(prog.getProgramPath())
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
150),
|
||||
// run count column
|
||||
@ -111,13 +115,17 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
Bundle.UserActivityPanel_TopProgramsTableModel_count_header(),
|
||||
(prog) -> {
|
||||
String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes());
|
||||
return new DefaultCellModel(runTimes);
|
||||
return new DefaultCellModel(runTimes)
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
80),
|
||||
// last run date column
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(),
|
||||
(prog) -> new DefaultCellModel(getFormatted(prog.getLastRun())),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(getFormatted(prog.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
150)
|
||||
))
|
||||
.setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName());
|
||||
@ -127,20 +135,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// domain column
|
||||
new ColumnModel<TopDomainsResult>(
|
||||
Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(),
|
||||
(recentDomain) -> new DefaultCellModel(recentDomain.getDomain()),
|
||||
(recentDomain) -> {
|
||||
return new DefaultCellModel(recentDomain.getDomain())
|
||||
.setPopupMenu(getPopup(recentDomain));
|
||||
},
|
||||
250),
|
||||
// count column
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopDomainsTableModel_count_header(),
|
||||
(recentDomain) -> {
|
||||
String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes());
|
||||
return new DefaultCellModel(visitTimes);
|
||||
return new DefaultCellModel(visitTimes)
|
||||
.setPopupMenu(getPopup(recentDomain));
|
||||
},
|
||||
100),
|
||||
// last accessed column
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(),
|
||||
(recentDomain) -> new DefaultCellModel(getFormatted(recentDomain.getLastVisit())),
|
||||
(recentDomain) -> {
|
||||
return new DefaultCellModel(getFormatted(recentDomain.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(recentDomain));
|
||||
},
|
||||
150)
|
||||
))
|
||||
.setKeyFunction((domain) -> domain.getDomain());
|
||||
@ -150,19 +165,28 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// search string column
|
||||
new ColumnModel<TopWebSearchResult>(
|
||||
Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(),
|
||||
(webSearch) -> new DefaultCellModel(webSearch.getSearchString()),
|
||||
(webSearch) -> {
|
||||
return new DefaultCellModel(webSearch.getSearchString())
|
||||
.setPopupMenu(getPopup(webSearch));
|
||||
},
|
||||
250
|
||||
),
|
||||
// last accessed
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(),
|
||||
(webSearch) -> new DefaultCellModel(getFormatted(webSearch.getDateAccessed())),
|
||||
(webSearch) -> {
|
||||
return new DefaultCellModel(getFormatted(webSearch.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(webSearch));
|
||||
},
|
||||
150
|
||||
),
|
||||
// translated value
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(),
|
||||
(webSearch) -> new DefaultCellModel(webSearch.getTranslatedResult()),
|
||||
(webSearch) -> {
|
||||
return new DefaultCellModel(webSearch.getTranslatedResult())
|
||||
.setPopupMenu(getPopup(webSearch));
|
||||
},
|
||||
250
|
||||
)
|
||||
))
|
||||
@ -173,13 +197,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// device id column
|
||||
new ColumnModel<TopDeviceAttachedResult>(
|
||||
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(),
|
||||
(device) -> new DefaultCellModel(device.getDeviceId()),
|
||||
(device) -> {
|
||||
return new DefaultCellModel(device.getDeviceId())
|
||||
.setPopupMenu(getPopup(device));
|
||||
},
|
||||
250
|
||||
),
|
||||
// last accessed
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(),
|
||||
(device) -> new DefaultCellModel(getFormatted(device.getDateAccessed())),
|
||||
(device) -> {
|
||||
return new DefaultCellModel(getFormatted(device.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(device));
|
||||
},
|
||||
150
|
||||
),
|
||||
// make and model
|
||||
@ -191,7 +221,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
String makeModelString = (make.isEmpty() || model.isEmpty())
|
||||
? make + model
|
||||
: String.format("%s - %s", make, model);
|
||||
return new DefaultCellModel(makeModelString);
|
||||
return new DefaultCellModel(makeModelString)
|
||||
.setPopupMenu(getPopup(device));
|
||||
},
|
||||
250
|
||||
)
|
||||
@ -203,13 +234,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// account type column
|
||||
new ColumnModel<TopAccountResult>(
|
||||
Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(),
|
||||
(account) -> new DefaultCellModel(account.getAccountType()),
|
||||
(account) -> {
|
||||
return new DefaultCellModel(account.getAccountType())
|
||||
.setPopupMenu(getPopup(account));
|
||||
},
|
||||
250
|
||||
),
|
||||
// last accessed
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(),
|
||||
(account) -> new DefaultCellModel(getFormatted(account.getLastAccess())),
|
||||
(account) -> {
|
||||
return new DefaultCellModel(getFormatted(account.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(account));
|
||||
},
|
||||
150
|
||||
)
|
||||
))
|
||||
@ -227,7 +264,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
|
||||
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
|
||||
private final UserActivitySummary userActivityData;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new UserActivityPanel.
|
||||
*/
|
||||
@ -239,7 +276,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
* Creates a new UserActivityPanel.
|
||||
*
|
||||
* @param userActivityData Class from which to obtain remaining user
|
||||
* activity data.
|
||||
* activity data.
|
||||
*/
|
||||
public UserActivityPanel(UserActivitySummary userActivityData) {
|
||||
super(userActivityData);
|
||||
@ -292,10 +329,23 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a base class of LastAccessedArtifact and provides the pertinent
|
||||
* menu items. going to artifact.
|
||||
*
|
||||
* @param record The LastAccessedArtifact instance.
|
||||
* @param navigateToArtifact Navigate right tot the artifact.
|
||||
* @return The menu items list containing one action or navigating to the
|
||||
* appropriate artifact and closing the data source summary dialog if open.
|
||||
*/
|
||||
private List<MenuItem> getPopup(LastAccessedArtifact record) {
|
||||
return record == null ? null : Arrays.asList(getArtifactNavigateItem(record.getArtifact()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries DataSourceTopProgramsSummary instance for short folder name.
|
||||
*
|
||||
* @param path The path for the application.
|
||||
* @param path The path for the application.
|
||||
* @param appName The application name.
|
||||
*
|
||||
* @return The underlying short folder name if one exists.
|
||||
@ -335,22 +385,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
|
||||
javax.swing.JPanel topProgramsTablePanel = topProgramsTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions1 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
|
||||
javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
|
||||
javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions2 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
|
||||
javax.swing.JLabel topWebSearchLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
|
||||
javax.swing.JPanel topWebSearches = topWebSearchesTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions3 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler6 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
|
||||
javax.swing.JLabel topDevicesAttachedLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler7 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
|
||||
javax.swing.JPanel recentDevicesAttached = topDevicesAttachedTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions4 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler8 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
|
||||
javax.swing.JLabel recentAccountsLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler9 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
|
||||
javax.swing.JPanel topAccounts = topAccountsTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions5 = new javax.swing.JLabel();
|
||||
|
||||
setLayout(new java.awt.BorderLayout());
|
||||
|
||||
@ -379,6 +434,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(topProgramsTablePanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions1, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions1.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions1);
|
||||
contentPanel.add(filler3);
|
||||
|
||||
recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -391,6 +449,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(recentDomainsTablePanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions2, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions2.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions2);
|
||||
contentPanel.add(filler4);
|
||||
|
||||
topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -403,6 +464,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(topWebSearches);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions3, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions3.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions3);
|
||||
contentPanel.add(filler6);
|
||||
|
||||
topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -415,6 +479,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(recentDevicesAttached);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions4, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions4.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions4);
|
||||
contentPanel.add(filler8);
|
||||
|
||||
recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -428,6 +495,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
topAccounts.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(topAccounts);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions5, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions5.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions5);
|
||||
|
||||
contentScrollPane.setViewportView(contentPanel);
|
||||
|
||||
add(contentScrollPane, java.awt.BorderLayout.CENTER);
|
||||
|
@ -304,4 +304,5 @@ public class BarChartPanel extends AbstractLoadableComponent<List<BarChartPanel.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,12 +20,22 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseEvent;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseListener;
|
||||
|
||||
/**
|
||||
* A Table cell renderer that renders a cell of a table based off of the
|
||||
@ -49,7 +59,7 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
* Constructor for a HorizontalAlign enum.
|
||||
*
|
||||
* @param jlabelAlignment The corresponding JLabel horizontal alignment
|
||||
* number.
|
||||
* number.
|
||||
*/
|
||||
HorizontalAlign(int jlabelAlignment) {
|
||||
this.jlabelAlignment = jlabelAlignment;
|
||||
@ -57,13 +67,60 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
/**
|
||||
* @return The corresponding JLabel horizontal alignment (i.e.
|
||||
* JLabel.LEFT).
|
||||
* JLabel.LEFT).
|
||||
*/
|
||||
int getJLabelAlignment() {
|
||||
return this.jlabelAlignment;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu item to be used within a popup menu.
|
||||
*/
|
||||
public interface MenuItem {
|
||||
|
||||
/**
|
||||
* @return The title for that popup menu item.
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* @return The action if that popup menu item is clicked.
|
||||
*/
|
||||
Runnable getAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of a menu item.
|
||||
*/
|
||||
public static class DefaultMenuItem implements MenuItem {
|
||||
|
||||
private final String title;
|
||||
private final Runnable action;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param title The title for the menu item.
|
||||
* @param action The action should the menu item be clicked.
|
||||
*/
|
||||
public DefaultMenuItem(String title, Runnable action) {
|
||||
this.title = title;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic interface for a cell model.
|
||||
*/
|
||||
@ -88,6 +145,12 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
* @return The insets for the cell text.
|
||||
*/
|
||||
Insets getInsets();
|
||||
|
||||
/**
|
||||
* @return The popup menu associated with this cell or null if no popup
|
||||
* menu should be shown for this cell.
|
||||
*/
|
||||
List<MenuItem> getPopupMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,6 +162,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
private String tooltip;
|
||||
private HorizontalAlign horizontalAlignment;
|
||||
private Insets insets;
|
||||
private List<MenuItem> popupMenu;
|
||||
private Supplier<List<MenuItem>> menuItemSupplier;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
@ -166,6 +231,41 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MenuItem> getPopupMenu() {
|
||||
if (popupMenu != null) {
|
||||
return Collections.unmodifiableList(popupMenu);
|
||||
}
|
||||
|
||||
if (menuItemSupplier != null) {
|
||||
return this.menuItemSupplier.get();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a function to lazy load the popup menu items.
|
||||
*
|
||||
* @param menuItemSupplier The lazy load function for popup items.
|
||||
* @return
|
||||
*/
|
||||
public DefaultCellModel setPopupMenuRetriever(Supplier<List<MenuItem>> menuItemSupplier) {
|
||||
this.menuItemSupplier = menuItemSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of items for a popup menu
|
||||
*
|
||||
* @param popupMenu
|
||||
* @return As a utility, returns this.
|
||||
*/
|
||||
public DefaultCellModel setPopupMenu(List<MenuItem> popupMenu) {
|
||||
this.popupMenu = popupMenu == null ? null : new ArrayList<>(popupMenu);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getText();
|
||||
@ -192,8 +292,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
* Customizes the jlabel to match the column model and cell model provided.
|
||||
*
|
||||
* @param defaultCell The cell to customize that will be displayed in the
|
||||
* jtable.
|
||||
* @param cellModel The cell model for this cell.
|
||||
* jtable.
|
||||
* @param cellModel The cell model for this cell.
|
||||
*
|
||||
* @return The provided defaultCell.
|
||||
*/
|
||||
@ -231,4 +331,42 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
return defaultCell;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default cell mouse listener that triggers popups for non-primary
|
||||
* button events.
|
||||
*/
|
||||
private static final CellMouseListener DEFAULT_CELL_MOUSE_LISTENER = new CellMouseListener() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(CellMouseEvent cellEvent) {
|
||||
if (cellEvent.getCellValue() instanceof CellModel && cellEvent.getMouseEvent().getButton() != MouseEvent.BUTTON1) {
|
||||
cellEvent.getTable().setRowSelectionInterval(cellEvent.getRow(), cellEvent.getRow());
|
||||
CellModel cellModel = (CellModel) cellEvent.getCellValue();
|
||||
List<MenuItem> menuItems = cellModel.getPopupMenu();
|
||||
|
||||
// if there are menu items, show a popup menu for
|
||||
// this item with all the menu items.
|
||||
if (CollectionUtils.isNotEmpty(menuItems)) {
|
||||
final JPopupMenu popupMenu = new JPopupMenu();
|
||||
for (MenuItem mItem : menuItems) {
|
||||
JMenuItem jMenuItem = new JMenuItem(mItem.getTitle());
|
||||
if (mItem.getAction() != null) {
|
||||
jMenuItem.addActionListener((evt) -> mItem.getAction().run());
|
||||
}
|
||||
popupMenu.add(jMenuItem);
|
||||
}
|
||||
popupMenu.show(cellEvent.getTable(), cellEvent.getMouseEvent().getX(), cellEvent.getMouseEvent().getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return The default cell mouse listener that triggers popups for
|
||||
* non-primary button events.
|
||||
*/
|
||||
public static CellMouseListener getMouseListener() {
|
||||
return DEFAULT_CELL_MOUSE_LISTENER;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@ -40,6 +42,85 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRendere
|
||||
*/
|
||||
public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
|
||||
/**
|
||||
* An event that wraps a swing MouseEvent also providing context within the
|
||||
* table cell.
|
||||
*/
|
||||
public static class CellMouseEvent {
|
||||
|
||||
private final MouseEvent e;
|
||||
private final JTable table;
|
||||
private final int row;
|
||||
private final int col;
|
||||
private final Object cellValue;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param e The underlying mouse event.
|
||||
* @param table The table that was the target of the mouse event.
|
||||
* @param row The row within the table that the event occurs.
|
||||
* @param col The column within the table that the event occurs.
|
||||
* @param cellValue The value within the cell.
|
||||
*/
|
||||
public CellMouseEvent(MouseEvent e, JTable table, int row, int col, Object cellValue) {
|
||||
this.e = e;
|
||||
this.table = table;
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.cellValue = cellValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The underlying mouse event.
|
||||
*/
|
||||
public MouseEvent getMouseEvent() {
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The table that was the target of the mouse event.
|
||||
*/
|
||||
public JTable getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The row within the table that the event occurs.
|
||||
*/
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The column within the table that the event occurs.
|
||||
*/
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value within the cell.
|
||||
*/
|
||||
public Object getCellValue() {
|
||||
return cellValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles mouse events for cells in the table.
|
||||
*/
|
||||
public interface CellMouseListener {
|
||||
|
||||
/**
|
||||
* Handles mouse events at a cell level for the table.
|
||||
*
|
||||
* @param e The event containing information about the cell, the mouse
|
||||
* event, and the table.
|
||||
*/
|
||||
void mouseClicked(CellMouseEvent e);
|
||||
}
|
||||
|
||||
/**
|
||||
* JTables don't allow displaying messages. So this LayerUI is used to
|
||||
* display the contents of a child JLabel. Inspired by TableWaitLayerTest
|
||||
@ -91,9 +172,9 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
/**
|
||||
* Constructor for a DataResultColumnModel.
|
||||
*
|
||||
* @param headerTitle The title for the column.
|
||||
* @param headerTitle The title for the column.
|
||||
* @param cellRenderer The method that generates a CellModel for the
|
||||
* column based on the data.
|
||||
* column based on the data.
|
||||
*/
|
||||
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer) {
|
||||
this(headerTitle, cellRenderer, null);
|
||||
@ -102,10 +183,10 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
/**
|
||||
* Constructor for a DataResultColumnModel.
|
||||
*
|
||||
* @param headerTitle The title for the column.
|
||||
* @param headerTitle The title for the column.
|
||||
* @param cellRenderer The method that generates a CellModel for the
|
||||
* column based on the data.
|
||||
* @param width The preferred width of the column.
|
||||
* column based on the data.
|
||||
* @param width The preferred width of the column.
|
||||
*/
|
||||
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer, Integer width) {
|
||||
this.headerTitle = headerTitle;
|
||||
@ -122,7 +203,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
|
||||
/**
|
||||
* @return The method that generates a CellModel for the column based on
|
||||
* the data.
|
||||
* the data.
|
||||
*/
|
||||
public Function<T, CellModel> getCellRenderer() {
|
||||
return cellRenderer;
|
||||
@ -197,14 +278,18 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
*/
|
||||
public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) {
|
||||
ListTableModel<T> tableModel = getTableModel(columns);
|
||||
JTablePanel<T> resultTable = new JTablePanel<>(tableModel);
|
||||
return resultTable.setColumnModel(getTableColumnModel(columns));
|
||||
JTablePanel<T> resultTable = new JTablePanel<>(tableModel)
|
||||
.setColumnModel(getTableColumnModel(columns))
|
||||
.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
|
||||
return resultTable;
|
||||
}
|
||||
|
||||
private JScrollPane tableScrollPane;
|
||||
private Overlay overlayLayer;
|
||||
private ListTableModel<T> tableModel;
|
||||
private JTable table;
|
||||
private CellMouseListener cellListener = null;
|
||||
private Function<T, ? extends Object> keyFunction = (rowItem) -> rowItem;
|
||||
|
||||
/**
|
||||
@ -222,6 +307,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
*/
|
||||
public JTablePanel() {
|
||||
initComponents();
|
||||
this.table.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// make sure click event isn't primary button and table is present
|
||||
if (cellListener != null) {
|
||||
int row = table.rowAtPoint(e.getPoint());
|
||||
int col = table.columnAtPoint(e.getPoint());
|
||||
|
||||
// make sure there is a value at the row,col of click event.
|
||||
if (tableModel != null
|
||||
&& row >= 0 && row < tableModel.getRowCount()
|
||||
&& col >= 0 && col < tableModel.getColumnCount()) {
|
||||
|
||||
Object cellValue = tableModel.getValueAt(row, col);
|
||||
cellListener.mouseClicked(new CellMouseEvent(e, table, row, col, cellValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,6 +347,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current listener for mouse events. The events provided to
|
||||
* this listener will have cell and table context.
|
||||
*/
|
||||
public CellMouseListener getCellListener() {
|
||||
return cellListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current listener for mouse events.
|
||||
*
|
||||
* @param cellListener The event listener that will receive these events
|
||||
* with cell and table context.
|
||||
* @return
|
||||
*/
|
||||
public JTablePanel<T> setCellListener(CellMouseListener cellListener) {
|
||||
this.cellListener = cellListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The underlying JTable's column model.
|
||||
*/
|
||||
@ -263,8 +388,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
|
||||
/**
|
||||
* @return The function for determining the key for a data row. This key is
|
||||
* used to maintain current selection in the table despite changing
|
||||
* rows.
|
||||
* used to maintain current selection in the table despite changing rows.
|
||||
*/
|
||||
public Function<T, ? extends Object> getKeyFunction() {
|
||||
return keyFunction;
|
||||
@ -295,7 +419,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
T prevValue = (tableRows != null && prevSelectedRow >= 0 && prevSelectedRow < tableRows.size())
|
||||
? this.tableModel.getDataRows().get(prevSelectedRow)
|
||||
: null;
|
||||
|
||||
|
||||
Object prevKeyValue = (prevValue == null) ? null : this.keyFunction.apply(prevValue);
|
||||
|
||||
// set the list of data to be shown as either the data or an empty list
|
||||
@ -330,7 +454,6 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
private void initComponents() {
|
||||
table = new JTable();
|
||||
table.getTableHeader().setReorderingAllowed(false);
|
||||
|
||||
overlayLayer = new Overlay();
|
||||
tableScrollPane = new JScrollPane(table);
|
||||
JLayer<JComponent> dualLayer = new JLayer<>(tableScrollPane, overlayLayer);
|
||||
|
@ -22,6 +22,7 @@ import com.google.common.cache.CacheLoader;
|
||||
import java.awt.Image;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -38,17 +39,22 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.openide.util.ImageUtilities;
|
||||
|
||||
/**
|
||||
* Loads a thumbnail for the given request. Thumbnail candidates are JPEG files
|
||||
* that have either TSK_WEB_DOWNLOAD or TSK_WEB_CACHE artifacts that match the
|
||||
* domain name (see the DomainSearch getArtifacts() API). JPEG files are sorted
|
||||
* Loads a thumbnail for the given request. Thumbnail candidates types are defined below.
|
||||
* These candidates types must be the source of either TSK_WEB_DOWNLOAD or
|
||||
* TSK_WEB_CACHE artifacts that match the
|
||||
* domain name (see the DomainSearch getArtifacts() API). Candidate files are sorted
|
||||
* by most recent if sourced from TSK_WEB_DOWNLOADs and by size if sourced from
|
||||
* TSK_WEB_CACHE artifacts. The first suitable thumbnail is selected.
|
||||
*/
|
||||
public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbnailRequest, Image> {
|
||||
|
||||
private static final String UNSUPPORTED_IMAGE = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
|
||||
private static final String JPG_EXTENSION = "jpg";
|
||||
private static final String JPG_MIME_TYPE = "image/jpeg";
|
||||
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("jpg", "svg", "png", "webp", "ico", "gif");
|
||||
private static final List<String> SUPPORTED_MIMETYPES = Arrays.asList(
|
||||
"image/gif", "image/jpeg", "image/png", "image/webp",
|
||||
"image/svg+xml", "image/vnd.microsoft.icon", "image/x-icon");
|
||||
private static final String ICO_EXTENSION = "ico";
|
||||
private static final List<String> ICO_MIMETYPES = Arrays.asList("image/vnd.microsoft.icon", "image/x-icon");
|
||||
private final DomainSearchArtifactsCache artifactsCache;
|
||||
|
||||
/**
|
||||
@ -75,8 +81,21 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest(
|
||||
caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD);
|
||||
final List<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest);
|
||||
final List<AbstractFile> webDownloadPictures = getJpegsFromWebDownload(caseDb, webDownloads);
|
||||
Collections.sort(webDownloadPictures, (file1, file2) -> Long.compare(file1.getCrtime(), file2.getCrtime()));
|
||||
final List<AbstractFile> webDownloadPictures = getCandidatesFromWebDownloads(caseDb, webDownloads);
|
||||
Collections.sort(webDownloadPictures, (file1, file2) -> {
|
||||
// Push ICO to the back of the sorted collection, so that ICO
|
||||
// is a last resort matching type.
|
||||
if (isIco(file1) && isIco(file2)) {
|
||||
return Long.compare(file1.getCrtime(), file2.getCrtime());
|
||||
} else if (isIco(file1)) {
|
||||
return 1;
|
||||
} else if (isIco(file2)) {
|
||||
return -1;
|
||||
} else {
|
||||
return Long.compare(file1.getCrtime(), file2.getCrtime());
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = webDownloadPictures.size() - 1; i >= 0; i--) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@ -92,8 +111,20 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest(
|
||||
caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE);
|
||||
final List<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest);
|
||||
final List<AbstractFile> webCachePictures = getJpegsFromWebCache(caseDb, webCacheArtifacts);
|
||||
Collections.sort(webCachePictures, (file1, file2) -> Long.compare(file1.getSize(), file2.getSize()));
|
||||
final List<AbstractFile> webCachePictures = getCandidatesFromWebCache(caseDb, webCacheArtifacts);
|
||||
Collections.sort(webCachePictures, (file1, file2) -> {
|
||||
// Push ICO to the back of the sorted collection, so that ICO
|
||||
// is a last resort matching type.
|
||||
if (isIco(file1) && isIco(file2)) {
|
||||
return Long.compare(file1.getSize(), file2.getSize());
|
||||
} else if (isIco(file1)) {
|
||||
return 1;
|
||||
} else if (isIco(file2)) {
|
||||
return -1;
|
||||
} else {
|
||||
return Long.compare(file1.getSize(), file2.getSize());
|
||||
}
|
||||
});
|
||||
for (int i = webCachePictures.size() - 1; i >= 0; i--) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@ -109,40 +140,45 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all JPEG source files from TSK_WEB_DOWNLOAD instances.
|
||||
* Finds all supported image files from TSK_WEB_DOWNLOAD instances.
|
||||
*
|
||||
* @param caseDb The case database being searched.
|
||||
* @param artifacts The list of artifacts to get jpegs from.
|
||||
* @param artifacts The list of artifacts to search.
|
||||
*
|
||||
* @return The list of AbstractFiles representing jpegs which were
|
||||
* @return The list of AbstractFiles representing supported images which were
|
||||
* associated with the artifacts.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private List<AbstractFile> getJpegsFromWebDownload(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
final List<AbstractFile> jpegs = new ArrayList<>();
|
||||
private List<AbstractFile> getCandidatesFromWebDownloads(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
final List<AbstractFile> candidates = new ArrayList<>();
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
final Content sourceContent = caseDb.getContentById(artifact.getObjectID());
|
||||
addIfJpeg(jpegs, sourceContent);
|
||||
addIfSupported(candidates, sourceContent);
|
||||
}
|
||||
return jpegs;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private boolean isIco(AbstractFile file) {
|
||||
return ICO_EXTENSION.equals(file.getNameExtension())
|
||||
|| ICO_MIMETYPES.contains(file.getMIMEType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all JPEG source files from TSK_WEB_CACHE instances.
|
||||
* Finds all supported image files from TSK_WEB_CACHE instances.
|
||||
*
|
||||
* @param caseDb The case database being searched.
|
||||
* @param artifacts The list of artifacts to get jpegs from.
|
||||
* @param artifacts The list of artifacts to get images from.
|
||||
*
|
||||
* @return The list of AbstractFiles representing jpegs which were
|
||||
* @return The list of AbstractFiles representing supported images which were
|
||||
* associated with the artifacts.
|
||||
*/
|
||||
private List<AbstractFile> getJpegsFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
private List<AbstractFile> getCandidatesFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID);
|
||||
final List<AbstractFile> jpegs = new ArrayList<>();
|
||||
final List<AbstractFile> candidates = new ArrayList<>();
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@ -150,24 +186,23 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID);
|
||||
if (tskPathId != null) {
|
||||
final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong());
|
||||
addIfJpeg(jpegs, sourceContent);
|
||||
addIfSupported(candidates, sourceContent);
|
||||
}
|
||||
}
|
||||
return jpegs;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the candidate source content is indeed a JPEG file.
|
||||
* Checks if the candidate source content is indeed a supported type.
|
||||
*
|
||||
* @param files The list of source content files which are jpegs to
|
||||
* add to.
|
||||
* @param files The list of source content files which are supported.
|
||||
* @param sourceContent The source content to check and possibly add.
|
||||
*/
|
||||
private void addIfJpeg(List<AbstractFile> files, Content sourceContent) {
|
||||
private void addIfSupported(List<AbstractFile> files, Content sourceContent) {
|
||||
if ((sourceContent instanceof AbstractFile) && !(sourceContent instanceof DataSource)) {
|
||||
final AbstractFile file = (AbstractFile) sourceContent;
|
||||
if (JPG_EXTENSION.equals(file.getNameExtension())
|
||||
|| JPG_MIME_TYPE.equals(file.getMIMEType())) {
|
||||
if (SUPPORTED_EXTENSIONS.contains(file.getNameExtension())
|
||||
|| SUPPORTED_MIMETYPES.contains(file.getMIMEType())) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ class ArtifactsWorker extends SwingWorker<List<BlackboardArtifact>, Void> {
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
listOfArtifacts.addAll(get());
|
||||
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.ArtifactSearchResultEvent(artifactType, listOfArtifacts));
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Exception while trying to get list of artifacts for Domain details for artifact type: "
|
||||
+ artifactType.getDisplayName() + " and domain: " + domain, ex);
|
||||
@ -83,6 +84,6 @@ class ArtifactsWorker extends SwingWorker<List<BlackboardArtifact>, Void> {
|
||||
//Worker was cancelled after previously finishing its background work, exception ignored to cut down on non-helpful logging
|
||||
}
|
||||
}
|
||||
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.ArtifactSearchResultEvent(artifactType, listOfArtifacts));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -333,7 +333,9 @@ public final class DiscoveryTopComponent extends TopComponent {
|
||||
descriptionText += Bundle.DiscoveryTopComponent_additionalFilters_text();
|
||||
}
|
||||
selectedDomainTabName = validateLastSelectedType(searchCompleteEvent);
|
||||
rightSplitPane.setBottomComponent(new DomainDetailsPanel(selectedDomainTabName));
|
||||
DomainDetailsPanel domainDetailsPanel = new DomainDetailsPanel();
|
||||
rightSplitPane.setBottomComponent(domainDetailsPanel);
|
||||
domainDetailsPanel.configureArtifactTabs(selectedDomainTabName);
|
||||
} else {
|
||||
rightSplitPane.setBottomComponent(new FileDetailsPanel());
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ final class DomainDetailsPanel extends JPanel {
|
||||
private ArtifactsWorker singleArtifactDomainWorker;
|
||||
private MiniTimelineWorker miniTimelineWorker;
|
||||
private String domain;
|
||||
private String selectedTabName;
|
||||
private String selectedTabName = null;
|
||||
|
||||
/**
|
||||
* Creates new form ArtifactDetailsPanel.
|
||||
@ -49,24 +49,23 @@ final class DomainDetailsPanel extends JPanel {
|
||||
* @param selectedTabName The name of the tab to select initially.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
DomainDetailsPanel(String selectedTabName) {
|
||||
DomainDetailsPanel() {
|
||||
initComponents();
|
||||
addArtifactTabs(selectedTabName);
|
||||
jTabbedPane1.add(Bundle.DomainDetailsPanel_miniTimelineTitle_text(), new MiniTimelinePanel());
|
||||
for (BlackboardArtifact.ARTIFACT_TYPE type : SearchData.Type.DOMAIN.getArtifactTypes()) {
|
||||
jTabbedPane1.add(type.getDisplayName(), new DomainArtifactsTabPanel(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tabs for each of the artifact types which we will be displaying.
|
||||
* Configure the tabs for each of the artifact types which we will be
|
||||
* displaying.
|
||||
*
|
||||
* @param tabName The name of the tab to select initially.
|
||||
*/
|
||||
@NbBundle.Messages({"DomainDetailsPanel.miniTimelineTitle.text=Mini Timeline"})
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private void addArtifactTabs(String tabName) {
|
||||
|
||||
jTabbedPane1.add(Bundle.DomainDetailsPanel_miniTimelineTitle_text(), new MiniTimelinePanel());
|
||||
for (BlackboardArtifact.ARTIFACT_TYPE type : SearchData.Type.DOMAIN.getArtifactTypes()) {
|
||||
jTabbedPane1.add(type.getDisplayName(), new DomainArtifactsTabPanel(type));
|
||||
}
|
||||
void configureArtifactTabs(String tabName) {
|
||||
selectedTabName = tabName;
|
||||
if (StringUtils.isBlank(selectedTabName)) {
|
||||
selectedTabName = Bundle.DomainDetailsPanel_miniTimelineTitle_text();
|
||||
|
@ -52,10 +52,12 @@ class MiniTimelineWorker extends SwingWorker<List<MiniTimelineResult>, Void> {
|
||||
|
||||
@Override
|
||||
protected List<MiniTimelineResult> doInBackground() throws Exception {
|
||||
List<MiniTimelineResult> results = new ArrayList<>();
|
||||
if (!StringUtils.isBlank(domain)) {
|
||||
DomainSearch domainSearch = new DomainSearch();
|
||||
try {
|
||||
return domainSearch.getAllArtifactsForDomain(Case.getCurrentCase().getSleuthkitCase(), domain);
|
||||
results.addAll(domainSearch.getAllArtifactsForDomain(Case.getCurrentCase().getSleuthkitCase(), domain));
|
||||
|
||||
} catch (DiscoveryException ex) {
|
||||
if (ex.getCause() instanceof InterruptedException) {
|
||||
logger.log(Level.INFO, "MiniTimeline search was cancelled or interrupted for domain: {0}", domain);
|
||||
@ -64,24 +66,22 @@ class MiniTimelineWorker extends SwingWorker<List<MiniTimelineResult>, Void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
List<MiniTimelineResult> results = null;
|
||||
List<MiniTimelineResult> results = new ArrayList<>();
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
results = get();
|
||||
results.addAll(get());
|
||||
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.MiniTimelineResultEvent(results));
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Exception while trying to get list of artifacts for Domain details for mini timeline view for domain: " + domain, ex);
|
||||
} catch (CancellationException ignored) {
|
||||
//Worker was cancelled after previously finishing its background work, exception ignored to cut down on non-helpful logging
|
||||
}
|
||||
}
|
||||
if (results == null) {
|
||||
results = new ArrayList<>();
|
||||
}
|
||||
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.MiniTimelineResultEvent(results));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -157,8 +157,8 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
if (!databaseIsInitialized()) {
|
||||
initializeDatabaseSchema();
|
||||
}
|
||||
|
||||
if (!CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) {
|
||||
|
||||
if (getVersion().compareTo(CURRENT_DB_SCHEMA_VERSION) < 0) {
|
||||
upgradeDatabaseSchema();
|
||||
}
|
||||
|
||||
@ -183,10 +183,15 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
if (conn == null) {
|
||||
throw new HealthMonitorException("Error getting database connection");
|
||||
}
|
||||
ResultSet resultSet = null;
|
||||
|
||||
try (Statement statement = conn.createStatement()) {
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
// NOTE: Due to a bug in the upgrade code, earlier versions of Autopsy will erroneously
|
||||
// run the upgrade if the database is a higher version than it expects. Therefore all
|
||||
// table changes must account for the possiblility of running multiple times.
|
||||
|
||||
// Upgrade from 1.0 to 1.1
|
||||
// Changes: user_data table added
|
||||
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) {
|
||||
@ -206,8 +211,13 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
// Changes: username added to user_data table
|
||||
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) {
|
||||
|
||||
// Add the user_data table
|
||||
statement.execute("ALTER TABLE user_data ADD COLUMN username text");
|
||||
resultSet = statement.executeQuery("SELECT column_name " +
|
||||
"FROM information_schema.columns " +
|
||||
"WHERE table_name='user_data' and column_name='username'");
|
||||
if (! resultSet.next()) {
|
||||
// Add the user_data table
|
||||
statement.execute("ALTER TABLE user_data ADD COLUMN username text");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the schema version
|
||||
@ -224,6 +234,13 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
}
|
||||
throw new HealthMonitorException("Error upgrading database", ex);
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException ex2) {
|
||||
logger.log(Level.SEVERE, "Error closing result set");
|
||||
}
|
||||
}
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException ex) {
|
||||
@ -967,7 +984,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
getInstance().writeCurrentStateToDatabase();
|
||||
}
|
||||
} catch (HealthMonitorException ex) {
|
||||
logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error recording health monitor metrics", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -300,7 +300,7 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(acceptedDevice, results.get(0).getDeviceModel());
|
||||
Assert.assertEquals("MAKE " + acceptedDevice, results.get(0).getDeviceMake());
|
||||
Assert.assertEquals("ID " + acceptedDevice, results.get(0).getDeviceId());
|
||||
Assert.assertEquals(time, results.get(0).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals(time, results.get(0).getLastAccessed().getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -353,7 +353,7 @@ public class UserActivitySummaryTest {
|
||||
List<TopDeviceAttachedResult> results = summary.getRecentDevices(dataSource, 10);
|
||||
|
||||
Assert.assertEquals(1, results.size());
|
||||
Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertTrue("ID1".equalsIgnoreCase(results.get(0).getDeviceId()));
|
||||
Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake()));
|
||||
Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel()));
|
||||
@ -403,9 +403,9 @@ public class UserActivitySummaryTest {
|
||||
|
||||
Assert.assertEquals("Expected two different search queries", 2, results.size());
|
||||
Assert.assertTrue(query1.equalsIgnoreCase(results.get(0).getSearchString()));
|
||||
Assert.assertEquals(DAY_SECONDS * 5, results.get(0).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * 5, results.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertTrue(query2.equalsIgnoreCase(results.get(1).getSearchString()));
|
||||
Assert.assertEquals(DAY_SECONDS * 3, results.get(1).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * 3, results.get(1).getLastAccessed().getTime() / 1000);
|
||||
}
|
||||
|
||||
private void webSearchTranslationTest(List<String> queries, boolean hasProvider, String translationSuffix)
|
||||
@ -599,11 +599,11 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(2, domains.size());
|
||||
|
||||
Assert.assertTrue("Expected " + domain1 + " to be first domain", domain1.equalsIgnoreCase(domains.get(0).getDomain()));
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS * 2, domains.get(0).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS * 2, domains.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 2L, domains.get(0).getVisitTimes());
|
||||
|
||||
Assert.assertTrue("Expected " + domain3 + " to be second domain", domain3.equalsIgnoreCase(domains.get(1).getDomain()));
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS, domains.get(1).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS, domains.get(1).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 1L, domains.get(1).getVisitTimes());
|
||||
}
|
||||
|
||||
@ -643,7 +643,7 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(1, domains.size());
|
||||
|
||||
Assert.assertTrue("Expected " + domain1 + " to be most recent domain", domain1.equalsIgnoreCase(domains.get(0).getDomain()));
|
||||
Assert.assertEquals(DAY_SECONDS, domains.get(0).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS, domains.get(0).getLastAccessed().getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -680,11 +680,11 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(2, domains.size());
|
||||
|
||||
Assert.assertTrue(domain1.equalsIgnoreCase(domains.get(1).getDomain()));
|
||||
Assert.assertEquals(6L, domains.get(1).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(6L, domains.get(1).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 2L, domains.get(1).getVisitTimes());
|
||||
|
||||
Assert.assertTrue(domain2.equalsIgnoreCase(domains.get(0).getDomain()));
|
||||
Assert.assertEquals(4L, domains.get(0).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(4L, domains.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 3L, domains.get(0).getVisitTimes());
|
||||
}
|
||||
|
||||
@ -869,7 +869,8 @@ public class UserActivitySummaryTest {
|
||||
|
||||
// since this may be somewhat variable
|
||||
Assert.assertTrue(expectedItem.getAccountType().equalsIgnoreCase(receivedItem.getAccountType()));
|
||||
Assert.assertEquals(expectedItem.getLastAccess().getTime(), receivedItem.getLastAccess().getTime());
|
||||
Assert.assertEquals(expectedItem.getLastAccessed().getTime(), receivedItem.getLastAccessed().getTime());
|
||||
Assert.assertEquals(expectedItem.getArtifact(), receivedItem.getArtifact());
|
||||
}
|
||||
}
|
||||
|
||||
@ -896,13 +897,13 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, email1,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), email1));
|
||||
|
||||
BlackboardArtifact email2 = getEmailArtifact(2, ds1, null, DAY_SECONDS);
|
||||
getRecentAccountsOneArtTest(ds1, email2,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), email2));
|
||||
|
||||
BlackboardArtifact email3 = getEmailArtifact(3, ds1, null, null);
|
||||
getRecentAccountsOneArtTest(ds1, email3, null);
|
||||
@ -911,19 +912,19 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, email4,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
|
||||
new Date(DAY_SECONDS * 2 * 1000)));
|
||||
new Date(DAY_SECONDS * 2 * 1000), email4));
|
||||
|
||||
BlackboardArtifact callog1 = getCallogArtifact(11, ds1, DAY_SECONDS, null);
|
||||
getRecentAccountsOneArtTest(ds1, callog1,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), callog1));
|
||||
|
||||
BlackboardArtifact callog2 = getCallogArtifact(12, ds1, null, DAY_SECONDS);
|
||||
getRecentAccountsOneArtTest(ds1, callog2,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), callog2));
|
||||
|
||||
BlackboardArtifact callog3 = getCallogArtifact(13, ds1, null, null);
|
||||
getRecentAccountsOneArtTest(ds1, callog3, null);
|
||||
@ -932,7 +933,7 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, callog4,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
|
||||
new Date(DAY_SECONDS * 2 * 1000)));
|
||||
new Date(DAY_SECONDS * 2 * 1000), callog4));
|
||||
|
||||
BlackboardArtifact message1 = getMessageArtifact(21, ds1, "Skype", null);
|
||||
getRecentAccountsOneArtTest(ds1, message1, null);
|
||||
@ -944,7 +945,7 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, message3, null);
|
||||
|
||||
BlackboardArtifact message4 = getMessageArtifact(24, ds1, "Skype", DAY_SECONDS);
|
||||
getRecentAccountsOneArtTest(ds1, message4, new TopAccountResult("Skype", new Date(DAY_SECONDS * 1000)));
|
||||
getRecentAccountsOneArtTest(ds1, message4, new TopAccountResult("Skype", new Date(DAY_SECONDS * 1000), message4));
|
||||
|
||||
}
|
||||
|
||||
@ -977,10 +978,10 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsTest(ds1, 10,
|
||||
Arrays.asList(email1, email2, email3, callog1, callog2, message1a, message1b, message2a, message2b),
|
||||
Arrays.asList(
|
||||
new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000)),
|
||||
new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000)),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000)),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), new Date((DAY_SECONDS + 13) * 1000))
|
||||
new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000), message2b),
|
||||
new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000), message1b),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000), callog2),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), new Date((DAY_SECONDS + 13) * 1000), email3)
|
||||
));
|
||||
}
|
||||
|
||||
@ -1156,17 +1157,17 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(0).getProgramName()));
|
||||
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(0).getProgramPath()));
|
||||
Assert.assertEquals((Long) 31L, results.get(0).getRunTimes());
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(0).getLastRun().getTime() / 1000));
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(0).getLastAccessed().getTime() / 1000));
|
||||
|
||||
Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(1).getProgramName()));
|
||||
Assert.assertTrue("/Program Files/etc/".equalsIgnoreCase(results.get(1).getProgramPath()));
|
||||
Assert.assertEquals((Long) 21L, results.get(1).getRunTimes());
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(1).getLastRun().getTime() / 1000));
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(1).getLastAccessed().getTime() / 1000));
|
||||
|
||||
Assert.assertTrue("program2.exe".equalsIgnoreCase(results.get(2).getProgramName()));
|
||||
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(2).getProgramPath()));
|
||||
Assert.assertEquals((Long) 10L, results.get(2).getRunTimes());
|
||||
Assert.assertEquals((Long) 22L, (Long) (results.get(2).getLastRun().getTime() / 1000));
|
||||
Assert.assertEquals((Long) 22L, (Long) (results.get(2).getLastAccessed().getTime() / 1000));
|
||||
}
|
||||
|
||||
private void assertProgramOrder(DataSource ds1, List<BlackboardArtifact> artifacts, List<String> programNamesReturned)
|
||||
|
@ -15,10 +15,7 @@ echo "Testing Autopsy..." && echo -en 'travis_fold:start:script.tests\\r'
|
||||
echo "Free Space:"
|
||||
echo `df -h .`
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = "osx" ]; then
|
||||
# if os x, just run it
|
||||
# ant -q test-no-regression
|
||||
elif [ "${TRAVIS_OS_NAME}" = "linux" ]; then
|
||||
if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
|
||||
# if linux use xvfb
|
||||
xvfb-run ant -q test-no-regression
|
||||
fi
|
||||
|
Loading…
x
Reference in New Issue
Block a user