diff --git a/Core/build.xml b/Core/build.xml
index e54c4eda97..43715c6778 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -197,7 +197,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java
index a911ac20d7..13e11d2a2d 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java
@@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
* A class to help display a communication artifact in a panel using a
* gridbaglayout.
*/
-final class CommunicationArtifactViewerHelper {
+public final class CommunicationArtifactViewerHelper {
// Number of columns in the gridbag layout.
private final static int MAX_COLS = 4;
@@ -63,12 +63,12 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param headerString Heading string to display.
*
* @return JLabel Heading label added.
*/
- static JLabel addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String headerString) {
+ public static JLabel addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String headerString) {
Insets savedInsets = constraints.insets;
@@ -109,6 +109,23 @@ final class CommunicationArtifactViewerHelper {
return headingLabel;
}
+ /**
+ * Add a key value row to the specified panel with the specified layout and
+ * constraints.
+ *
+ *
+ * @param panel Panel to update.
+ * @param gridbagLayout Layout to use.
+ * @param constraints Constraints to use.
+ * @param keyString Key name to display.
+ * @param valueString Value string to display.
+ *
+ */
+ public static void addNameValueRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString, String valueString) {
+ addKey(panel, gridbagLayout, constraints, keyString);
+ addValue(panel, gridbagLayout, constraints, valueString);
+ }
+
/**
* Adds the given component to the panel.
*
@@ -116,7 +133,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param component Component to add.
*/
static void addComponent(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, JComponent component) {
@@ -132,7 +149,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
*/
static void addLineEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
// Place the filler just past the last column.
@@ -159,9 +176,9 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
*/
- static void addPageEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
+ public static void addPageEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
constraints.gridx = 0;
@@ -185,7 +202,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
*/
static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
constraints.gridy++;
@@ -203,7 +220,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param keyString Key name to display.
*
* @return Label added.
@@ -217,7 +234,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param keyString Key name to display.
* @param gridx column index, must be less than MAX_COLS - 1.
*
@@ -246,8 +263,8 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
- * @param keyString Value string to display.
+ * @param constraints Constraints to use.
+ * @param valueString Value string to display.
*
* @return Label added.
*/
@@ -260,7 +277,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param keyString Value string to display.
* @param gridx Column index, must be less than MAX_COLS;
*
@@ -367,7 +384,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param accountIdentifier Account identifier to search the persona.
*
* @return List of AccountPersonaSearcherData objects.
@@ -435,7 +452,7 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
- * @param constraints Constrains to use.
+ * @param constraints Constraints to use.
* @param contactId Contact name to display.
*
* @return A JLabel with the contact information.
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java
index 8c0c89a865..0c9e41afd6 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java
@@ -54,15 +54,14 @@ import com.google.gson.JsonArray;
import java.util.Locale;
import java.util.Map;
import javax.swing.SwingUtilities;
+import org.sleuthkit.autopsy.discovery.ui.AbstractArtifactDetailsPanel;
//import org.sleuthkit.autopsy.contentviewers.Bundle;
/**
- * This class displays a Blackboard artifact as a table listing all it's
- * attributes names and values.
+ * This class displays a Blackboard artifact as a table of its attributes.
*/
-
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
-public class DefaultArtifactContentViewer extends javax.swing.JPanel implements ArtifactContentViewer {
+public class DefaultArtifactContentViewer extends AbstractArtifactDetailsPanel implements ArtifactContentViewer {
@NbBundle.Messages({
"DefaultArtifactContentViewer.attrsTableHeader.type=Type",
@@ -71,11 +70,11 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
"DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database",
"DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database"
})
-
+
private final static Logger logger = Logger.getLogger(DefaultArtifactContentViewer.class.getName());
-
+
private static final long serialVersionUID = 1L;
-
+
private static final String[] COLUMN_HEADERS = {
Bundle.DefaultArtifactContentViewer_attrsTableHeader_type(),
Bundle.DefaultArtifactContentViewer_attrsTableHeader_value(),
@@ -124,7 +123,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
// do nothing
}
- @Override
+ @Override
public void columnMarginChanged(ChangeEvent e) {
updateRowHeights(); //When the user changes column width we may need to resize row height
}
@@ -153,12 +152,12 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
Component comp = resultsTable.prepareRenderer(
resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex);
final int rowHeight;
- if (comp instanceof JTextArea) {
+ if (comp instanceof JTextArea) {
final JTextArea tc = (JTextArea) comp;
final View rootView = tc.getUI().getRootView(tc);
java.awt.Insets i = tc.getInsets();
rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex)
- .getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders
+ .getWidth() - (i.left + i.right + CELL_RIGHT_MARGIN), //current width minus borders
Integer.MAX_VALUE);
rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS);
} else {
@@ -267,7 +266,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
* Resets the components to an empty view state.
*/
private void resetComponents() {
-
+
((DefaultTableModel) resultsTable.getModel()).setRowCount(0);
}
@@ -279,7 +278,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
@Override
public void setArtifact(BlackboardArtifact artifact) {
try {
- ResultsTableArtifact resultsTableArtifact = new ResultsTableArtifact(artifact, artifact.getParent());
+ ResultsTableArtifact resultsTableArtifact = artifact == null ? null : new ResultsTableArtifact(artifact, artifact.getParent());
SwingUtilities.invokeLater(new Runnable() {
@Override
@@ -289,7 +288,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
});
} catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
+ logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
}
}
@@ -301,7 +300,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
}
/**
- * This class is a container to hold the data necessary for the artifact
+ * This class is a container to hold the data necessary for the artifact
* being viewed.
*/
private class ResultsTableArtifact {
@@ -340,20 +339,20 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
*/
String value;
switch (attr.getAttributeType().getValueType()) {
-
+
// Use Autopsy date formatting settings, not TSK defaults
case DATETIME:
value = epochTimeToString(attr.getValueLong());
break;
- case JSON:
+ case JSON:
// Get the attribute's JSON value and convert to indented multiline display string
String jsonVal = attr.getValueString();
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(jsonVal).getAsJsonObject();
-
+
value = toJsonDisplayString(json, "");
break;
-
+
case STRING:
case INTEGER:
case LONG:
@@ -398,43 +397,43 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
String getArtifactDisplayName() {
return artifactDisplayName;
}
-
+
private static final String INDENT_RIGHT = " ";
private static final String NEW_LINE = "\n";
-
+
/**
* Recursively converts a JSON element into an indented multi-line
* display string.
*
- * @param element JSON element to convert
+ * @param element JSON element to convert
* @param startIndent Starting indentation for the element.
*
* @return A multi-line display string.
*/
private String toJsonDisplayString(JsonElement element, String startIndent) {
-
+
StringBuilder sb = new StringBuilder("");
JsonObject obj = element.getAsJsonObject();
for (Map.Entry entry : obj.entrySet()) {
- appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb );
+ appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb);
}
String returnString = sb.toString();
- if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) {
+ if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) {
returnString = returnString.substring(NEW_LINE.length());
}
return returnString;
}
-
-
+
/**
- * Converts the given JSON element into string and appends to the given string builder.
- *
+ * Converts the given JSON element into string and appends to the given
+ * string builder.
+ *
* @param jsonKey
* @param jsonElement
* @param startIndent Starting indentation for the element.
- * @param sb String builder to append to.
+ * @param sb String builder to append to.
*/
private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) {
if (jsonElement.isJsonArray()) {
@@ -463,11 +462,12 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey));
}
}
-
+
/**
* Converts epoch time to readable string.
- *
+ *
* @param epochTime epoch time value to be converted to string.
+ *
* @return String with human readable time.
*/
private String epochTimeToString(long epochTime) {
@@ -482,21 +482,20 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements
}
/**
- * Updates the table view with the given artifact data.
- *
+ * Updates the table view with the given artifact data.
+ *
* It should be called on EDT.
*
* @param resultsTableArtifact Artifact data to display in the view.
*/
private void updateView(ResultsTableArtifact resultsTableArtifact) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-
DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel());
- tModel.setDataVector(resultsTableArtifact.getRows(), COLUMN_HEADERS);
+ String[][] rows = resultsTableArtifact == null ? new String[0][0] : resultsTableArtifact.getRows();
+ tModel.setDataVector(rows, COLUMN_HEADERS);
updateColumnSizes();
updateRowHeights();
resultsTable.clearSelection();
-
this.setCursor(null);
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java
index 6ccc70c920..b015dbf3ad 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java
@@ -357,7 +357,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID())
- || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID())) {
+ || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID())
+ || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID())
+ || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) {
return 3;
} else {
return 6;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
index c7ccf32099..34a6859292 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
@@ -128,8 +128,8 @@ public class BlackboardArtifactNode extends AbstractContentNode artifact.getSleuthkitCase().getContentById(objectID));
+ if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID()) {
+ content = getPathIdFile(artifact);
+ }
if (content == null) {
- return Lookups.fixed(artifact);
- } else {
- return Lookups.fixed(artifact, content);
+ content = contentCache.get(objectID, () -> artifact.getSleuthkitCase().getContentById(objectID));
}
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, MessageFormat.format("Error getting source content (artifact objID={0}", artifact.getId()), ex); //NON-NLS
- return Lookups.fixed(artifact);
+ content = null;
}
+ if (content == null) {
+ return Lookups.fixed(artifact);
+ } else {
+ return Lookups.fixed(artifact, content);
+ }
+
+ }
+
+ /**
+ * Private helper method to allow content specified in a path id attribute
+ * to be retrieved.
+ *
+ * @param artifact The artifact for which content may be specified as a tsk
+ * path attribute.
+ *
+ * @return The Content specified by the artifact's path id attribute or null
+ * if there was no content available.
+ *
+ * @throws ExecutionException Error retrieving the file specified by the
+ * path id from the cache.
+ */
+ private static Content getPathIdFile(BlackboardArtifact artifact) throws ExecutionException {
+ try {
+ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
+ if (attribute != null) {
+ return contentCache.get(attribute.getValueLong(), () -> artifact.getSleuthkitCase().getContentById(attribute.getValueLong()));
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.WARNING, MessageFormat.format("Error getting content for path id attrbiute for artifact: ", artifact.getId()), ex); //NON-NLS
+ }
+ return null;
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TimelineSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TimelineSummary.java
new file mode 100644
index 0000000000..1d634211be
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TimelineSummary.java
@@ -0,0 +1,318 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.datamodel;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import org.joda.time.Interval;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
+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.core.UserPreferences;
+
+/**
+ * Provides data source summary information pertaining to Timeline data.
+ */
+public class TimelineSummary implements DefaultUpdateGovernor {
+
+ private static final long DAY_SECS = 24 * 60 * 60;
+ private static final Set INGEST_JOB_EVENTS = new HashSet<>(
+ Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED));
+
+ private static final Set FILE_SYSTEM_EVENTS
+ = new HashSet<>(Arrays.asList(
+ TimelineEventType.FILE_MODIFIED,
+ TimelineEventType.FILE_ACCESSED,
+ TimelineEventType.FILE_CREATED,
+ TimelineEventType.FILE_CHANGED));
+
+ private final SleuthkitCaseProvider caseProvider;
+ private final Supplier timeZoneProvider;
+
+ /**
+ * Default constructor.
+ */
+ public TimelineSummary() {
+ this(SleuthkitCaseProvider.DEFAULT, () -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()));
+ }
+
+ /**
+ * Construct object with given SleuthkitCaseProvider
+ *
+ * @param caseProvider SleuthkitCaseProvider provider, cannot be null.
+ * @param timeZoneProvider The timezone provider, cannot be null.
+ */
+ public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier timeZoneProvider) {
+ this.caseProvider = caseProvider;
+ this.timeZoneProvider = timeZoneProvider;
+ }
+
+ @Override
+ public boolean isRefreshRequired(ModuleContentEvent evt) {
+ return true;
+ }
+
+ @Override
+ public boolean isRefreshRequired(AbstractFile file) {
+ return true;
+ }
+
+ @Override
+ public boolean isRefreshRequired(IngestManager.IngestJobEvent evt) {
+ return (evt != null && INGEST_JOB_EVENTS.contains(evt));
+ }
+
+ @Override
+ public Set getIngestJobEventUpdates() {
+ return INGEST_JOB_EVENTS;
+ }
+
+ /**
+ * Retrieves timeline summary data.
+ *
+ * @param dataSource The data source for which timeline data will be
+ * retrieved.
+ * @param recentDaysNum The maximum number of most recent days' activity to
+ * include.
+ * @return The retrieved data.
+ * @throws SleuthkitCaseProviderException
+ * @throws TskCoreException
+ */
+ public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException {
+ TimeZone timeZone = this.timeZoneProvider.get();
+ TimelineManager timelineManager = this.caseProvider.get().getTimelineManager();
+
+ // get a mapping of days from epoch to the activity for that day
+ Map dateCounts = getTimelineEventsByDay(dataSource, timelineManager, timeZone);
+
+ // get minimum and maximum usage date by iterating through
+ Long minDay = null;
+ Long maxDay = null;
+ for (long daysFromEpoch : dateCounts.keySet()) {
+ minDay = (minDay == null) ? daysFromEpoch : Math.min(minDay, daysFromEpoch);
+ maxDay = (maxDay == null) ? daysFromEpoch : Math.max(maxDay, daysFromEpoch);
+ }
+
+ // if no min date or max date, no usage; return null.
+ if (minDay == null || maxDay == null) {
+ return null;
+ }
+
+ Date minDate = new Date(minDay * 1000 * DAY_SECS);
+ Date maxDate = new Date(maxDay * 1000 * DAY_SECS);
+
+ // The minimum recent day will be within recentDaysNum from the maximum day
+ // (+1 since maxDay included) or the minimum day of activity
+ long minRecentDay = Math.max(maxDay - recentDaysNum + 1, minDay);
+
+ // get most recent days activity
+ List mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay);
+
+ return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt);
+ }
+
+ /**
+ * Given activity by day, converts to most recent days' activity handling
+ * empty values.
+ *
+ * @param dateCounts The day from epoch mapped to activity amounts for that
+ * day.
+ * @param minRecentDay The minimum recent day in days from epoch.
+ * @param maxDay The maximum recent day in days from epoch;
+ * @return The most recent daily activity amounts.
+ */
+ private List getMostRecentActivityAmounts(Map dateCounts, long minRecentDay, long maxDay) {
+ List mostRecentActivityAmt = new ArrayList<>();
+
+ for (long curRecentDay = minRecentDay; curRecentDay <= maxDay; curRecentDay++) {
+ DailyActivityAmount prevCounts = dateCounts.get(curRecentDay);
+ DailyActivityAmount countsHandleNotFound = prevCounts != null
+ ? prevCounts
+ : new DailyActivityAmount(new Date(curRecentDay * DAY_SECS * 1000), 0, 0);
+
+ mostRecentActivityAmt.add(countsHandleNotFound);
+ }
+ return mostRecentActivityAmt;
+ }
+
+ /**
+ * Fetches timeline events per day for a particular data source.
+ *
+ * @param dataSource The data source.
+ * @param timelineManager The timeline manager to use while fetching the
+ * data.
+ * @param timeZone The time zone to use to determine which day activity
+ * belongs.
+ * @return A Map mapping days from epoch to the activity for that day.
+ * @throws TskCoreException
+ */
+ private Map 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());
+
+ // get events for data source
+ long curRunTime = System.currentTimeMillis();
+ List events = timelineManager.getEvents(new Interval(1, curRunTime), dataSourceRootFilter);
+
+ // get counts of events per day (left is file system events, right is everything else)
+ Map dateCounts = new HashMap<>();
+ for (TimelineEvent evt : events) {
+ long curSecondsFromEpoch = evt.getTime();
+ long curDaysFromEpoch = Instant.ofEpochMilli(curSecondsFromEpoch * 1000)
+ .atZone(timeZone.toZoneId())
+ .toLocalDate()
+ .toEpochDay();
+
+ DailyActivityAmount prevAmt = dateCounts.get(curDaysFromEpoch);
+ long prevFileEvtCount = prevAmt == null ? 0 : prevAmt.getFileActivityCount();
+ long prevArtifactEvtCount = prevAmt == null ? 0 : prevAmt.getArtifactActivityCount();
+ Date thisDay = prevAmt == null ? new Date(curDaysFromEpoch * 1000 * DAY_SECS) : prevAmt.getDay();
+
+ boolean isFileEvt = FILE_SYSTEM_EVENTS.contains(evt.getEventType());
+ long curFileEvtCount = prevFileEvtCount + (isFileEvt ? 1 : 0);
+ long curArtifactEvtCount = prevArtifactEvtCount + (isFileEvt ? 0 : 1);
+
+ dateCounts.put(curDaysFromEpoch, new DailyActivityAmount(thisDay, curFileEvtCount, curArtifactEvtCount));
+ }
+
+ return dateCounts;
+ }
+
+ /**
+ * All the data to be represented in the timeline summary tab.
+ */
+ public static class TimelineSummaryData {
+
+ private final Date minDate;
+ private final Date maxDate;
+ private final List histogramActivity;
+
+ /**
+ * Main constructor.
+ *
+ * @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.
+ */
+ TimelineSummaryData(Date minDate, Date maxDate, List recentDaysActivity) {
+ this.minDate = minDate;
+ this.maxDate = maxDate;
+ this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity);
+ }
+
+ /**
+ * @return Earliest usage date recorded for the data source.
+ */
+ public Date getMinDate() {
+ return minDate;
+ }
+
+ /**
+ * @return Latest usage date recorded for the data source.
+ */
+ public Date getMaxDate() {
+ return maxDate;
+ }
+
+ /**
+ * @return A list of activity prior to and including the latest usage
+ * date by day.
+ */
+ public List getMostRecentDaysActivity() {
+ return histogramActivity;
+ }
+ }
+
+ /**
+ * Represents the amount of usage based on timeline events for a day.
+ */
+ public static class DailyActivityAmount {
+
+ private final Date day;
+ private final long fileActivityCount;
+ private final long artifactActivityCount;
+
+ /**
+ * Main constructor.
+ *
+ * @param day The day for which activity is being measured.
+ * @param fileActivityCount The amount of file activity timeline events.
+ * @param artifactActivityCount The amount of artifact timeline events.
+ */
+ DailyActivityAmount(Date day, long fileActivityCount, long artifactActivityCount) {
+ this.day = day;
+ this.fileActivityCount = fileActivityCount;
+ this.artifactActivityCount = artifactActivityCount;
+ }
+
+ /**
+ * @return The day for which activity is being measured.
+ */
+ public Date getDay() {
+ return day;
+ }
+
+ /**
+ * @return The amount of file activity timeline events.
+ */
+ public long getFileActivityCount() {
+ return fileActivityCount;
+ }
+
+ /**
+ * @return The amount of artifact timeline events.
+ */
+ public long getArtifactActivityCount() {
+ return artifactActivityCount;
+ }
+
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties
index 94b497d2ff..5d9e6adf0c 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties
@@ -42,3 +42,4 @@ RecentFilesPanel.attachmentLabel.text=Recent Attachments
PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
+TimelinePanel.activityRangeLabel.text=Activity Range
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
index f03f55df38..4997d067f1 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
@@ -3,6 +3,7 @@ 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.
+ContainerPanel_setFieldsForNonImageDataSource_na=N/A
CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryDialog.closeButton.text=Close
ContainerPanel.displayNameLabel.text=Display Name:
@@ -47,6 +48,7 @@ DataSourceSummaryTabbedPane_detailsTab_title=Container
DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History
DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases
DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files
+DataSourceSummaryTabbedPane_timelineTab_title=Timeline
DataSourceSummaryTabbedPane_typesTab_title=Types
DataSourceSummaryTabbedPane_userActivityTab_title=User Activity
PastCasesPanel_caseColumn_title=Case
@@ -64,6 +66,11 @@ SizeRepresentationUtil_units_kilobytes=\ kB
SizeRepresentationUtil_units_megabytes=\ MB
SizeRepresentationUtil_units_petabytes=\ PB
SizeRepresentationUtil_units_terabytes=\ TB
+TimelinePanel_earliestLabel_title=Earliest
+TimelinePanel_latestLabel_title=Latest
+TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events
+TimlinePanel_last30DaysChart_fileEvts_title=File Events
+TimlinePanel_last30DaysChart_title=Last 30 Days
TypesPanel_artifactsTypesPieChart_title=Artifact Types
TypesPanel_fileMimeTypesChart_audio_title=Audio
TypesPanel_fileMimeTypesChart_documents_title=Documents
@@ -95,6 +102,7 @@ RecentFilesPanel.attachmentLabel.text=Recent Attachments
PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
+TimelinePanel.activityRangeLabel.text=Activity Range
UserActivityPanel_noDataExists=No communication data exists
UserActivityPanel_tab_title=User Activity
UserActivityPanel_TopAccountTableModel_accountType_header=Account Type
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java
index a852a0b886..bc331d952b 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java
@@ -26,6 +26,7 @@ import java.util.Set;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.table.DefaultTableModel;
+import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
@@ -52,7 +53,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
/**
* Main constructor.
*
- * @param dataSource The original datasource.
+ * @param dataSource The original datasource.
* @param unallocatedFilesSize The unallocated file size.
*/
ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize) {
@@ -165,8 +166,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize) {
clearTableValues();
if (selectedDataSource != null) {
- unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(unallocatedFilesSize));
- timeZoneValue.setText(selectedDataSource.getTimeZone());
displayNameValue.setText(selectedDataSource.getName());
originalNameValue.setText(selectedDataSource.getName());
deviceIdValue.setText(selectedDataSource.getDeviceId());
@@ -178,24 +177,48 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
}
if (selectedDataSource instanceof Image) {
- setFieldsForImage((Image) selectedDataSource);
+ setFieldsForImage((Image) selectedDataSource, unallocatedFilesSize);
+ } else {
+ setFieldsForNonImageDataSource();
}
}
-
+
this.repaint();
}
+ @Messages({
+ "ContainerPanel_setFieldsForNonImageDataSource_na=N/A"
+ })
+ private void setFieldsForNonImageDataSource() {
+ String NA = Bundle.ContainerPanel_setFieldsForNonImageDataSource_na();
+
+ unallocatedSizeValue.setText(NA);
+ imageTypeValue.setText(NA);
+ sizeValue.setText(NA);
+ sectorSizeValue.setText(NA);
+ timeZoneValue.setText(NA);
+
+ ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{NA});
+
+ md5HashValue.setText(NA);
+ sha1HashValue.setText(NA);
+ sha256HashValue.setText(NA);
+ }
+
/**
* Sets text fields for an image. This should be called after
* clearTableValues and before updateFieldVisibility to ensure the proper
* rendering.
*
* @param selectedImage The selected image.
+ * @param unallocatedFilesSize Unallocated file size in bytes.
*/
- private void setFieldsForImage(Image selectedImage) {
+ private void setFieldsForImage(Image selectedImage, Long unallocatedFilesSize) {
+ unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(unallocatedFilesSize));
imageTypeValue.setText(selectedImage.getType().getName());
sizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSize()));
sectorSizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSsize()));
+ timeZoneValue.setText(selectedImage.getTimeZone());
for (String path : selectedImage.getPaths()) {
((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path});
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java
index 55320f7dbb..3670124fa2 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java
@@ -38,7 +38,8 @@ import org.sleuthkit.datamodel.DataSource;
"DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History",
"DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files",
"DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases",
- "DataSourceSummaryTabbedPane_analysisTab_title=Analysis"
+ "DataSourceSummaryTabbedPane_analysisTab_title=Analysis",
+ "DataSourceSummaryTabbedPane_timelineTab_title=Timeline"
})
public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
@@ -56,10 +57,10 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
/**
* Main constructor.
*
- * @param tabTitle The title of the tab.
- * @param component The component to be displayed.
+ * @param tabTitle The title of the tab.
+ * @param component The component to be displayed.
* @param onDataSource The function to be called on a new data source.
- * @param onClose Called to cleanup resources when closing tabs.
+ * @param onClose Called to cleanup resources when closing tabs.
*/
DataSourceTab(String tabTitle, Component component, Consumer onDataSource, Runnable onClose) {
this.tabTitle = tabTitle;
@@ -72,7 +73,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* Main constructor.
*
* @param tabTitle The title of the tab.
- * @param panel The component to be displayed in the tab.
+ * @param panel The component to be displayed in the tab.
*/
DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
this.tabTitle = tabTitle;
@@ -123,6 +124,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_analysisTab_title(), new AnalysisPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_recentFileTab_title(), new RecentFilesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_pastCasesTab_title(), new PastCasesPanel()),
+ new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_timelineTab_title(), new TimelinePanel()),
// do nothing on closing
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel, ingestHistoryPanel::setDataSource, () -> {
}),
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java
index 76d36f4785..2400334ee3 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java
@@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Function;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory;
@@ -28,7 +27,6 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
-import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
@@ -103,31 +101,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
* @param result The result.
*/
private void handleResult(DataFetchResult result) {
- showResultWithModuleCheck(notableFileTable, getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME);
- showResultWithModuleCheck(sameIdTable, getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME);
- }
-
- /**
- * Given an input data fetch result, creates an error result if the original
- * is an error. Otherwise, uses the getSubResult function on the underlying
- * data to create a new DataFetchResult.
- *
- * @param inputResult The input result.
- * @param getSubComponent The means of getting the data given the original
- * data.
- *
- * @return The new result with the error of the original or the processed
- * data.
- */
- private DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) {
- if (inputResult == null) {
- return null;
- } else if (inputResult.getResultType() == ResultType.SUCCESS) {
- O innerData = (inputResult.getData() == null) ? null : getSubResult.apply(inputResult.getData());
- return DataFetchResult.getSuccessResult(innerData);
- } else {
- return DataFetchResult.getErrorResult(inputResult.getException());
- }
+ showResultWithModuleCheck(notableFileTable, DataFetchResult.getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME);
+ showResultWithModuleCheck(sameIdTable, DataFetchResult.getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME);
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.form
new file mode 100644
index 0000000000..e3493d7a0d
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.form
@@ -0,0 +1,214 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java
new file mode 100644
index 0000000000..87f170ccab
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java
@@ -0,0 +1,263 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.ui;
+
+import java.awt.Color;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import org.apache.commons.collections.CollectionUtils;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount;
+import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.BarChartItem;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.BarChartSeries;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.OrderedKey;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
+import org.sleuthkit.datamodel.DataSource;
+
+/**
+ * A tab shown in data source summary displaying information about a data
+ * source's timeline events.
+ */
+@Messages({
+ "TimelinePanel_earliestLabel_title=Earliest",
+ "TimelinePanel_latestLabel_title=Latest",
+ "TimlinePanel_last30DaysChart_title=Last 30 Days",
+ "TimlinePanel_last30DaysChart_fileEvts_title=File Events",
+ "TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",})
+public class TimelinePanel extends BaseDataSourceSummaryPanel {
+
+ 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");
+ private static final int MOST_RECENT_DAYS_COUNT = 30;
+
+ /**
+ * Creates a DateFormat formatter that uses UTC for time zone.
+ *
+ * @param formatString The date format string.
+ * @return The data format.
+ */
+ private static DateFormat getUtcFormat(String formatString) {
+ return new SimpleDateFormat(formatString, Locale.getDefault());
+ }
+
+ // components displayed in the tab
+ private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
+ 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(), "", "");
+
+ // all loadable components on this tab
+ private final List> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart);
+
+ // actions to load data for this tab
+ private final List> dataFetchComponents;
+
+ public TimelinePanel() {
+ this(new TimelineSummary());
+ }
+
+ /**
+ * Creates new form PastCasesPanel
+ */
+ public TimelinePanel(TimelineSummary timelineData) {
+ // set up data acquisition methods
+ dataFetchComponents = Arrays.asList(
+ new DataFetchWorker.DataFetchComponents<>(
+ (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT),
+ (result) -> handleResult(result))
+ );
+
+ initComponents();
+ }
+
+ /**
+ * Formats a date using a DateFormat. In the event that the date is null,
+ * returns a null string.
+ *
+ * @param date The date to format.
+ * @param formatter The DateFormat to use to format the date.
+ * @return The formatted string generated from the formatter or null if the
+ * date is null.
+ */
+ 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);
+
+ /**
+ * Converts DailyActivityAmount data retrieved from TimelineSummary into
+ * data to be displayed as a bar chart.
+ *
+ * @param recentDaysActivity The data retrieved from TimelineSummary.
+ * @return The data to be displayed in the BarChart.
+ */
+ private List parseChartData(List recentDaysActivity) {
+ // if no data, return null indicating no result.
+ if (CollectionUtils.isEmpty(recentDaysActivity)) {
+ return null;
+ }
+
+ // Create a bar chart item for each recent days activity item
+ List fileEvtCounts = new ArrayList<>();
+ List artifactEvtCounts = new ArrayList<>();
+
+ for (int i = 0; i < recentDaysActivity.size(); i++) {
+ DailyActivityAmount curItem = recentDaysActivity.get(i);
+
+ long fileAmt = curItem.getFileActivityCount();
+ long artifactAmt = curItem.getArtifactActivityCount() * 100;
+ String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1)
+ ? formatDate(curItem.getDay(), CHART_FORMAT) : "";
+
+ OrderedKey thisKey = new OrderedKey(formattedDate, i);
+ fileEvtCounts.add(new BarChartItem(thisKey, fileAmt));
+ artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt));
+ }
+
+ return Arrays.asList(
+ new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts),
+ new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts));
+ }
+
+ /**
+ * Handles displaying the result for each displayable item in the
+ * TimelinePanel by breaking the TimelineSummaryData result into its
+ * constituent parts and then sending each data item to the pertinent
+ * component.
+ *
+ * @param result The result to be displayed on this tab.
+ */
+ private void handleResult(DataFetchResult result) {
+ 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())));
+ }
+
+ @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();
+ super.close();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane();
+ javax.swing.JPanel mainContentPanel = new javax.swing.JPanel();
+ javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
+ javax.swing.JLabel activityRangeLabel = 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 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.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));
+ mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS));
+
+ ingestRunningPanel.setAlignmentX(0.0F);
+ ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
+ ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
+ ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
+ mainContentPanel.add(ingestRunningPanel);
+
+ activityRangeLabel.setFont(new java.awt.Font("Segoe UI", 1, 12)); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(activityRangeLabel, org.openide.util.NbBundle.getMessage(TimelinePanel.class, "TimelinePanel.activityRangeLabel.text")); // NOI18N
+ mainContentPanel.add(activityRangeLabel);
+ activityRangeLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(TimelinePanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
+
+ filler1.setAlignmentX(0.0F);
+ mainContentPanel.add(filler1);
+
+ earliestLabelPanel.setAlignmentX(0.0F);
+ earliestLabelPanel.setMaximumSize(new java.awt.Dimension(32767, 20));
+ earliestLabelPanel.setMinimumSize(new java.awt.Dimension(100, 20));
+ earliestLabelPanel.setPreferredSize(new java.awt.Dimension(100, 20));
+ mainContentPanel.add(earliestLabelPanel);
+
+ latestLabelPanel.setAlignmentX(0.0F);
+ latestLabelPanel.setMaximumSize(new java.awt.Dimension(32767, 20));
+ latestLabelPanel.setMinimumSize(new java.awt.Dimension(100, 20));
+ latestLabelPanel.setPreferredSize(new java.awt.Dimension(100, 20));
+ mainContentPanel.add(latestLabelPanel);
+
+ 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);
+
+ filler5.setAlignmentX(0.0F);
+ mainContentPanel.add(filler5);
+
+ mainScrollPane.setViewportView(mainContentPanel);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
index 448bf6bbea..247aa0c304 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.ui;
-import java.awt.BorderLayout;
import java.awt.Color;
import java.sql.SQLException;
import java.text.DecimalFormat;
@@ -30,7 +29,6 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import javax.swing.JLabel;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory;
@@ -40,13 +38,13 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.MimeTypeSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
-import org.sleuthkit.autopsy.datasourcesummary.uiutils.AbstractLoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
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.datasourcesummary.uiutils.PieChartPanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
@@ -78,46 +76,6 @@ import org.sleuthkit.datamodel.TskCoreException;
"TypesPanel_sizeLabel_title=Size"})
class TypesPanel extends BaseDataSourceSummaryPanel {
- /**
- * A label that allows for displaying loading messages and can be used with
- * a DataFetchResult. Text displays as ":".
- */
- private static class LoadableLabel extends AbstractLoadableComponent {
-
- private static final long serialVersionUID = 1L;
-
- private final JLabel label = new JLabel();
- private final String key;
-
- /**
- * Main constructor for the label.
- *
- * @param key The key to be displayed.
- */
- LoadableLabel(String key) {
- this.key = key;
- setLayout(new BorderLayout());
- add(label, BorderLayout.CENTER);
- this.showResults(null);
- }
-
- private void setValue(String value) {
- String formattedKey = StringUtils.isBlank(key) ? "" : key;
- String formattedValue = StringUtils.isBlank(value) ? "" : value;
- label.setText(String.format("%s: %s", formattedKey, formattedValue));
- }
-
- @Override
- protected void setMessage(boolean visible, String message) {
- setValue(message);
- }
-
- @Override
- protected void setResults(String data) {
- setValue(data);
- }
- }
-
/**
* Data for types pie chart.
*/
@@ -129,9 +87,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
/**
* Main constructor.
*
- * @param pieSlices The pie slices.
+ * @param pieSlices The pie slices.
* @param usefulContent True if this is useful content; false if there
- * is 0 mime type information.
+ * is 0 mime type information.
*/
public TypesPieChartData(List pieSlices, boolean usefulContent) {
this.pieSlices = pieSlices;
@@ -165,9 +123,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
/**
* Main constructor.
*
- * @param label The label for this slice.
+ * @param label The label for this slice.
* @param mimeTypes The mime types associated with this slice.
- * @param color The color associated with this slice.
+ * @param color The color associated with this slice.
*/
TypesPieCategory(String label, Set mimeTypes, Color color) {
this.label = label;
@@ -178,9 +136,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
/**
* Constructor that accepts FileTypeCategory.
*
- * @param label The label for this slice.
+ * @param label The label for this slice.
* @param mimeTypes The mime types associated with this slice.
- * @param color The color associated with this slice.
+ * @param color The color associated with this slice.
*/
TypesPieCategory(String label, FileTypeCategory fileCategory, Color color) {
this(label, fileCategory.getMediaTypes(), color);
@@ -278,8 +236,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
/**
* Creates a new TypesPanel.
*
- * @param mimeTypeData The service for mime types.
- * @param typeData The service for file types data.
+ * @param mimeTypeData The service for mime types.
+ * @param typeData The service for file types data.
* @param containerData The service for container information.
*/
public TypesPanel(
@@ -358,7 +316,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
* Gets all the data for the file type pie chart.
*
* @param mimeTypeData The means of acquiring data.
- * @param dataSource The datasource.
+ * @param dataSource The datasource.
*
* @return The pie chart items.
*/
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java
new file mode 100644
index 0000000000..3f3822f7c2
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java
@@ -0,0 +1,307 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.util.Collections;
+import java.util.List;
+import javax.swing.JLabel;
+import org.apache.commons.collections4.CollectionUtils;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.CategoryPlot;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.renderer.category.BarRenderer;
+import org.jfree.chart.renderer.category.StandardBarPainter;
+import org.jfree.data.category.DefaultCategoryDataset;
+
+/**
+ * A bar chart panel.
+ */
+public class BarChartPanel extends AbstractLoadableComponent> {
+
+ /**
+ * Represents a series in a bar chart where all items pertain to one
+ * category.
+ */
+ public static class BarChartSeries {
+
+ private final Comparable> key;
+ private final Color color;
+ private final List items;
+
+ /**
+ * Main constructor.
+ *
+ * @param color The color for this series.
+ * @param items The bars to be displayed for this series.
+ */
+ public BarChartSeries(Comparable> key, Color color, List items) {
+ this.key = key;
+ this.color = color;
+ this.items = (items == null) ? Collections.emptyList() : Collections.unmodifiableList(items);
+ }
+
+ /**
+ * @return The color for this series.
+ */
+ public Color getColor() {
+ return color;
+ }
+
+ /**
+ * @return The bars to be displayed for this series.
+ */
+ public List getItems() {
+ return items;
+ }
+
+ /**
+ * @return The key for this item.
+ */
+ public Comparable> getKey() {
+ return key;
+ }
+ }
+
+ /**
+ * An individual bar to be displayed in the bar chart.
+ */
+ public static class BarChartItem {
+
+ private final Comparable> key;
+ private final double value;
+
+ /**
+ * Main constructor.
+ *
+ * @param label The key for this bar. Also serves as the label using
+ * toString().
+ * @param value The value for this item.
+ */
+ public BarChartItem(Comparable> key, double value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * @return The key for this item.
+ */
+ public Comparable> getKey() {
+ return key;
+ }
+
+ /**
+ * @return The value for this item.
+ */
+ public double getValue() {
+ return value;
+ }
+ }
+
+ /**
+ * JFreeChart bar charts don't preserve the order of bars provided to the
+ * chart, but instead uses the comparable nature to order items. This
+ * provides order using a provided index as well as the value for the axis.
+ */
+ public static class OrderedKey implements Comparable {
+
+ private final Object keyValue;
+ private final int keyIndex;
+
+ /**
+ * Main constructor.
+ *
+ * @param keyValue The value for the key to be displayed in the domain
+ * axis.
+ * @param keyIndex The index at which it will be displayed.
+ */
+ public OrderedKey(Object keyValue, int keyIndex) {
+ this.keyValue = keyValue;
+ this.keyIndex = keyIndex;
+ }
+
+ /**
+ * @return The value for the key to be displayed in the domain axis.
+ */
+ Object getKeyValue() {
+ return keyValue;
+ }
+
+ /**
+ * @return The index at which it will be displayed.
+ */
+ int getKeyIndex() {
+ return keyIndex;
+ }
+
+ @Override
+ public int compareTo(OrderedKey o) {
+ // this will have a higher value than null.
+ if (o == null) {
+ return 1;
+ }
+
+ // compare by index
+ return Integer.compare(this.getKeyIndex(), o.getKeyIndex());
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final OrderedKey other = (OrderedKey) obj;
+ if (this.keyIndex != other.keyIndex) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ // use toString on the key.
+ return this.getKeyValue() == null ? null : this.getKeyValue().toString();
+ }
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Font DEFAULT_FONT = new JLabel().getFont();
+ private static final Font DEFAULT_HEADER_FONT = new Font(DEFAULT_FONT.getName(), DEFAULT_FONT.getStyle(), (int) (DEFAULT_FONT.getSize() * 1.5));
+
+ private final ChartMessageOverlay overlay = new ChartMessageOverlay();
+ private final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
+ private final JFreeChart chart;
+ private final CategoryPlot plot;
+
+ /**
+ * Main constructor assuming null values for all items.
+ */
+ public BarChartPanel() {
+ this(null, null, null);
+ }
+
+ /**
+ * Main constructor for the pie chart.
+ *
+ * @param title The title for this pie chart.
+ * @param categoryLabel The x-axis label.
+ * @param valueLabel The y-axis label.
+ */
+ public BarChartPanel(String title, String categoryLabel, String valueLabel) {
+ this.chart = ChartFactory.createStackedBarChart(
+ title,
+ categoryLabel,
+ valueLabel,
+ dataset,
+ PlotOrientation.VERTICAL,
+ true, false, false);
+
+ // set style to match autopsy components
+ chart.setBackgroundPaint(null);
+ chart.getTitle().setFont(DEFAULT_HEADER_FONT);
+
+ this.plot = ((CategoryPlot) chart.getPlot());
+ this.plot.getRenderer().setBaseItemLabelFont(DEFAULT_FONT);
+ plot.setBackgroundPaint(null);
+ plot.setOutlinePaint(null);
+
+ // hide y axis labels
+ ValueAxis range = plot.getRangeAxis();
+ range.setVisible(false);
+
+ // make sure x axis labels don't get cut off
+ plot.getDomainAxis().setMaximumCategoryLabelWidthRatio(10);
+
+ ((BarRenderer) plot.getRenderer()).setBarPainter(new StandardBarPainter());
+
+ // Create Panel
+ ChartPanel panel = new ChartPanel(chart);
+ panel.addOverlay(overlay);
+ panel.setPopupMenu(null);
+
+ this.setLayout(new BorderLayout());
+ this.add(panel, BorderLayout.CENTER);
+ }
+
+ /**
+ * @return The title for this chart if one exists.
+ */
+ public String getTitle() {
+ return (this.chart == null || this.chart.getTitle() == null)
+ ? null
+ : this.chart.getTitle().getText();
+ }
+
+ /**
+ * Sets the title for this pie chart.
+ *
+ * @param title The title.
+ *
+ * @return As a utility, returns this.
+ */
+ public BarChartPanel setTitle(String title) {
+ this.chart.getTitle().setText(title);
+ return this;
+ }
+
+ @Override
+ protected void setMessage(boolean visible, String message) {
+ this.overlay.setVisible(visible);
+ this.overlay.setMessage(message);
+ }
+
+ @Override
+ protected void setResults(List data) {
+ this.dataset.clear();
+
+ if (CollectionUtils.isNotEmpty(data)) {
+ for (int s = 0; s < data.size(); s++) {
+ BarChartPanel.BarChartSeries series = data.get(s);
+ if (series != null && CollectionUtils.isNotEmpty(series.getItems())) {
+ if (series.getColor() != null) {
+ this.plot.getRenderer().setSeriesPaint(s, series.getColor());
+ }
+
+ for (int i = 0; i < series.getItems().size(); i++) {
+ BarChartItem bar = series.getItems().get(i);
+ this.dataset.setValue(bar.getValue(), series.getKey(), bar.getKey());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ChartMessageOverlay.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ChartMessageOverlay.java
new file mode 100644
index 0000000000..2e21dfb796
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ChartMessageOverlay.java
@@ -0,0 +1,63 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.Graphics2D;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.panel.AbstractOverlay;
+import org.jfree.chart.panel.Overlay;
+
+/**
+ * A JFreeChart message overlay that can show a message for the purposes of the
+ * LoadableComponent.
+ */
+class ChartMessageOverlay extends AbstractOverlay implements Overlay {
+
+ private static final long serialVersionUID = 1L;
+ private final BaseMessageOverlay overlay = new BaseMessageOverlay();
+
+ // multiply this value by the smaller dimension (height or width) of the component
+ // to determine width of text to be displayed.
+ private static final double MESSAGE_WIDTH_FACTOR = .6;
+
+ /**
+ * Sets this layer visible when painted. In order to be shown in UI, this
+ * component needs to be repainted.
+ *
+ * @param visible Whether or not it is visible.
+ */
+ void setVisible(boolean visible) {
+ overlay.setVisible(visible);
+ }
+
+ /**
+ * Sets the message to be displayed in the child jlabel.
+ *
+ * @param message The message to be displayed.
+ */
+ void setMessage(String message) {
+ overlay.setMessage(message);
+ }
+
+ @Override
+ public void paintOverlay(Graphics2D gd, ChartPanel cp) {
+ int labelWidth = (int) (Math.min(cp.getWidth(), cp.getHeight()) * MESSAGE_WIDTH_FACTOR);
+ overlay.paintOverlay(gd, cp.getWidth(), cp.getHeight(), labelWidth);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java
index 93cc24f5fe..6e2d8f4991 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java
@@ -18,6 +18,8 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+import java.util.function.Function;
+
/**
* The result of a loading process.
*/
@@ -30,6 +32,29 @@ public final class DataFetchResult {
SUCCESS, ERROR
}
+ /**
+ * A utility method that, given an input data fetch result, creates an error
+ * result if the original is an error. Otherwise, uses the getSubResult
+ * function on the underlying data to create a new DataFetchResult.
+ *
+ * @param inputResult The input result.
+ * @param getSubComponent The means of getting the data given the original
+ * data.
+ *
+ * @return The new result with the error of the original or the processed
+ * data.
+ */
+ public static DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) {
+ if (inputResult == null) {
+ return null;
+ } else if (inputResult.getResultType() == ResultType.SUCCESS) {
+ O innerData = (inputResult.getData() == null) ? null : getSubResult.apply(inputResult.getData());
+ return DataFetchResult.getSuccessResult(innerData);
+ } else {
+ return DataFetchResult.getErrorResult(inputResult.getException());
+ }
+ }
+
/**
* Creates a DataFetchResult of loaded data including the data.
*
@@ -59,9 +84,8 @@ public final class DataFetchResult {
/**
* Main constructor for the DataLoadingResult.
*
- * @param state The state of the result.
- * @param data If the result is SUCCESS, the data related to this
- * result.
+ * @param state The state of the result.
+ * @param data If the result is SUCCESS, the data related to this result.
* @param exception If the result is ERROR, the related exception.
*/
private DataFetchResult(ResultType state, R data, Throwable exception) {
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/LoadableLabel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/LoadableLabel.java
new file mode 100644
index 0000000000..3fdf81ad18
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/LoadableLabel.java
@@ -0,0 +1,63 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.BorderLayout;
+import javax.swing.JLabel;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A label that allows for displaying loading messages and can be used with a
+ * DataFetchResult. Text displays as ":".
+ */
+public class LoadableLabel extends AbstractLoadableComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ private final JLabel label = new JLabel();
+ private final String key;
+
+ /**
+ * Main constructor for the label.
+ *
+ * @param key The key to be displayed.
+ */
+ public LoadableLabel(String key) {
+ this.key = key;
+ setLayout(new BorderLayout());
+ add(label, BorderLayout.CENTER);
+ this.showResults(null);
+ }
+
+ private void setValue(String value) {
+ String formattedKey = StringUtils.isBlank(key) ? "" : key;
+ String formattedValue = StringUtils.isBlank(value) ? "" : value;
+ label.setText(String.format("%s: %s", formattedKey, formattedValue));
+ }
+
+ @Override
+ protected void setMessage(boolean visible, String message) {
+ setValue(message);
+ }
+
+ @Override
+ protected void setResults(String data) {
+ setValue(data);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
index 971cb83367..fa0d00dab6 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
@@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
-import java.awt.Graphics2D;
import java.text.DecimalFormat;
import java.util.List;
import javax.swing.JLabel;
@@ -30,8 +29,6 @@ import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.PieSectionLabelGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
-import org.jfree.chart.panel.AbstractOverlay;
-import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.PiePlot;
import org.jfree.data.general.DefaultPieDataset;
import org.openide.util.NbBundle.Messages;
@@ -59,7 +56,7 @@ public class PieChartPanel extends AbstractLoadableComponent data, String message) {
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java
index e224c02d51..0b9a0a98c1 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java
@@ -19,12 +19,14 @@
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.eventbus.EventBus;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
import org.sleuthkit.autopsy.discovery.search.SearchData.Type;
import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Class to handle event bus and events for discovery tool.
@@ -88,13 +90,13 @@ public final class DiscoveryEventUtils {
//no arg constructor
}
}
-
+
/**
- * Event to signal that any background tasks currently running should
- * be cancelled.
+ * Event to signal that any background tasks currently running should be
+ * cancelled.
*/
public static final class CancelBackgroundTasksEvent {
-
+
public CancelBackgroundTasksEvent() {
//no-arg constructor
}
@@ -124,6 +126,30 @@ public final class DiscoveryEventUtils {
}
}
+ /**
+ * Event to signal that the list should be populated.
+ */
+ public static final class PopulateDomainTabsEvent {
+
+ private final String domain;
+
+ /**
+ * Construct a new PopulateDomainTabsEvent.
+ */
+ public PopulateDomainTabsEvent(String domain) {
+ this.domain = domain;
+ }
+
+ /**
+ * Get the domain for the details area.
+ *
+ * @return The the domain for the details area.
+ */
+ public String getDomain() {
+ return domain;
+ }
+ }
+
/**
* Event to signal the completion of a search being performed.
*/
@@ -203,6 +229,47 @@ public final class DiscoveryEventUtils {
}
+ /**
+ * Event to signal the completion of a search being performed.
+ */
+ public static final class ArtifactSearchResultEvent {
+
+ private final List listOfArtifacts = new ArrayList<>();
+ private final BlackboardArtifact.ARTIFACT_TYPE artifactType;
+
+ /**
+ * Construct a new ArtifactSearchResultEvent with a list of specified
+ * artifacts and an artifact type.
+ *
+ * @param artifactType The type of artifacts in the list.
+ * @param listOfArtifacts The list of artifacts retrieved.
+ */
+ public ArtifactSearchResultEvent(BlackboardArtifact.ARTIFACT_TYPE artifactType, List listOfArtifacts) {
+ if (listOfArtifacts != null) {
+ this.listOfArtifacts.addAll(listOfArtifacts);
+ }
+ this.artifactType = artifactType;
+ }
+
+ /**
+ * Get the list of artifacts included in the event.
+ *
+ * @return The list of artifacts retrieved.
+ */
+ public List getListOfArtifacts() {
+ return Collections.unmodifiableList(listOfArtifacts);
+ }
+
+ /**
+ * Get the type of BlackboardArtifact type of which exist in the list.
+ *
+ * @return The BlackboardArtifact type of which exist in the list.
+ */
+ public BlackboardArtifact.ARTIFACT_TYPE getArtifactType() {
+ return artifactType;
+ }
+ }
+
/**
* Event to signal the completion of page retrieval and include the page
* contents.
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java
index 9505cfa5e2..f39bb56634 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java
@@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -217,6 +218,15 @@ public class SearchFiltering {
this.types = types;
}
+ /**
+ * Get the list of artifact types specified by the filter.
+ *
+ * @return The list of artifact types specified by the filter.
+ */
+ public List getTypes() {
+ return Collections.unmodifiableList(types);
+ }
+
@Override
public String getWhereClause() {
StringJoiner joiner = new StringJoiner(",");
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java
new file mode 100644
index 0000000000..b36e93728a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java
@@ -0,0 +1,41 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.ui;
+
+import javax.swing.JPanel;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+
+/**
+ * Class for ensuring all ArtifactDetailsPanels have a setArtifact method.
+ *
+ */
+public abstract class AbstractArtifactDetailsPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Called to display the contents of the given artifact.
+ *
+ * @param artifact the artifact to display.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ abstract public void setArtifact(BlackboardArtifact artifact);
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java
index 3e4ab45592..7fc964ad52 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java
@@ -24,6 +24,7 @@ import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.event.ListSelectionListener;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
/**
* Abstract class extending JPanel for filter controls.
@@ -41,6 +42,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
* selected, null to indicate leaving selected items
* unchanged or that there are no items to select.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract void configurePanel(boolean selected, int[] indicesSelected);
/**
@@ -48,6 +50,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
*
* @return The JCheckBox which enables and disables this filter.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract JCheckBox getCheckbox();
/**
@@ -57,6 +60,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
* @return The JList which contains the values available for selection for
* this filter.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract JList> getList();
/**
@@ -65,6 +69,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
*
* @return The JLabel to display under the JCheckBox.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract JLabel getAdditionalLabel();
/**
@@ -73,6 +78,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
* @return If the settings are invalid returns the error that has occurred,
* otherwise returns empty string.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract String checkForError();
/**
@@ -82,6 +88,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
* @param actionlistener The listener for the checkbox selection events.
* @param listListener The listener for the list selection events.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
void addListeners(ActionListener actionListener, ListSelectionListener listListener) {
if (getCheckbox() != null) {
getCheckbox().addActionListener(actionListener);
@@ -97,11 +104,13 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
* @return The AbstractFilter for the selected settings, null if the
* settings are not in use.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract AbstractFilter getFilter();
/**
* Remove listeners from the checkbox and the list if they exist.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
void removeListeners() {
if (getCheckbox() != null) {
for (ActionListener listener : getCheckbox().getActionListeners()) {
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java
index 6e8137a6ed..c8f71a5d99 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java
@@ -32,6 +32,7 @@ import javax.swing.JSplitPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType;
import org.sleuthkit.autopsy.discovery.search.Group;
import org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod;
@@ -65,6 +66,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
/**
* Setup necessary for implementations of this abstract class.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
AbstractFiltersPanel() {
firstColumnPanel.setLayout(new GridBagLayout());
secondColumnPanel.setLayout(new GridBagLayout());
@@ -75,6 +77,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The type of results this panel filters.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
abstract SearchData.Type getType();
/**
@@ -88,7 +91,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
* list, null if none are selected.
* @param column The column to add the DiscoveryFilterPanel to.
*/
- final synchronized void addFilter(AbstractDiscoveryFilterPanel filterPanel, boolean isSelected, int[] indicesSelected, int column) {
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ final void addFilter(AbstractDiscoveryFilterPanel filterPanel, boolean isSelected, int[] indicesSelected, int column) {
if (!isInitialized) {
constraints.gridy = 0;
constraints.anchor = GridBagConstraints.FIRST_LINE_START;
@@ -132,6 +136,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @param splitPane The JSplitPane which the columns are added to.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
final void addPanelsToScrollPane(JSplitPane splitPane) {
splitPane.setLeftComponent(firstColumnPanel);
splitPane.setRightComponent(secondColumnPanel);
@@ -142,7 +147,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
/**
* Clear the filters from the panel
*/
- final synchronized void clearFilters() {
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ final void clearFilters() {
for (AbstractDiscoveryFilterPanel filterPanel : filters) {
filterPanel.removeListeners();
}
@@ -159,6 +165,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
* column.
* @param columnIndex The column to add the Component to.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void addToGridBagLayout(Component componentToAdd, Component additionalComponentToAdd, int columnIndex) {
addToColumn(componentToAdd, columnIndex);
if (additionalComponentToAdd != null) {
@@ -174,6 +181,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
* @param component The Component to add.
* @param columnNumber The column to add the Component to.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void addToColumn(Component component, int columnNumber) {
if (columnNumber == 0) {
firstColumnPanel.add(component, constraints);
@@ -186,7 +194,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
* Check if the fields are valid, and fire a property change event to
* indicate any errors.
*/
- synchronized void validateFields() {
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void validateFields() {
String errorString = null;
for (AbstractDiscoveryFilterPanel filterPanel : filters) {
errorString = filterPanel.checkForError();
@@ -197,6 +206,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
firePropertyChange("FilterError", null, errorString);
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
public void actionPerformed(ActionEvent e) {
validateFields();
@@ -209,6 +219,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return True if the ObjectsDetectedFilter is supported, false otherwise.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
boolean isObjectsFilterSupported() {
for (AbstractDiscoveryFilterPanel filter : filters) {
if (filter instanceof ObjectDetectedFilterPanel) {
@@ -223,6 +234,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return True if the HashSetFilter is supported, false otherwise.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
boolean isHashSetFilterSupported() {
for (AbstractDiscoveryFilterPanel filter : filters) {
if (filter instanceof HashSetFilterPanel) {
@@ -237,6 +249,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return True if the InterestingItemsFilter is supported, false otherwise.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
boolean isInterestingItemsFilterSupported() {
for (AbstractDiscoveryFilterPanel filter : filters) {
if (filter instanceof InterestingItemsFilterPanel) {
@@ -251,8 +264,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The list of filters selected by the user.
*/
- synchronized List getFilters() {
-
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ List getFilters() {
List filtersToUse = new ArrayList<>();
if (getType() != SearchData.Type.DOMAIN) { //Domain type does not have a file type
filtersToUse.add(new SearchFiltering.FileTypeFilter(getType()));
@@ -268,6 +281,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
return filtersToUse;
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
public void valueChanged(ListSelectionEvent evt) {
if (!evt.getValueIsAdjusting()) {
@@ -282,6 +296,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The most recently used sorting method.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
SortingMethod getLastSortingMethod() {
return lastSortingMethod;
}
@@ -291,6 +306,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @param lastSortingMethod The most recently used sorting method.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
final void setLastSortingMethod(SortingMethod lastSortingMethod) {
this.lastSortingMethod = lastSortingMethod;
}
@@ -300,6 +316,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The most recently used grouping attribute.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
GroupingAttributeType getLastGroupingAttributeType() {
return lastGroupingAttributeType;
}
@@ -310,6 +327,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
* @param lastGroupingAttributeType The most recently used grouping
* attribute.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
final void setLastGroupingAttributeType(GroupingAttributeType lastGroupingAttributeType) {
this.lastGroupingAttributeType = lastGroupingAttributeType;
}
@@ -319,6 +337,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The most recently used group sorting algorithm.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
Group.GroupSortingAlgorithm getLastGroupSortingAlg() {
return lastGroupSortingAlg;
}
@@ -329,6 +348,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
* @param lastGroupSortingAlg The most recently used group sorting
* algorithm.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
final void setLastGroupSortingAlg(Group.GroupSortingAlgorithm lastGroupSortingAlg) {
this.lastGroupSortingAlg = lastGroupSortingAlg;
}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java
index 089c18bf13..e6a1ccaaed 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java
@@ -26,6 +26,7 @@ import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.search.SearchData;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter;
import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -40,6 +41,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
/**
* Creates new form ArtifactTypeFilterPanel
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
ArtifactTypeFilterPanel() {
initComponents();
setUpArtifactTypeFilter();
@@ -49,6 +51,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
/**
* Initialize the data source filter.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void setUpArtifactTypeFilter() {
int count = 0;
DefaultListModel artifactTypeModel = (DefaultListModel) artifactList.getModel();
@@ -104,6 +107,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
artifactList.setEnabled(artifactTypeCheckbox.isSelected());
}//GEN-LAST:event_artifactTypeCheckboxActionPerformed
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
void configurePanel(boolean selected, int[] indicesSelected) {
artifactTypeCheckbox.setSelected(selected);
@@ -119,11 +123,13 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
JCheckBox getCheckbox() {
return artifactTypeCheckbox;
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
JList> getList() {
return artifactList;
@@ -134,6 +140,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
return null;
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@NbBundle.Messages({"ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected."})
@Override
String checkForError() {
@@ -143,6 +150,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
return "";
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
AbstractFilter getFilter() {
if (artifactTypeCheckbox.isSelected() && !artifactList.getSelectedValuesList().isEmpty()) {
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form
new file mode 100644
index 0000000000..d5c94ead08
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form
@@ -0,0 +1,59 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java
new file mode 100644
index 0000000000..6235761d6e
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java
@@ -0,0 +1,346 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import javax.swing.JPanel;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.StringUtils;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Panel to display list of artifacts for selected domain.
+ *
+ */
+class ArtifactsListPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+ private final DomainArtifactTableModel tableModel;
+ private static final Logger logger = Logger.getLogger(ArtifactsListPanel.class.getName());
+
+ /**
+ * Creates new form ArtifactsListPanel.
+ *
+ * @param artifactType The type of artifact displayed in this table.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ ArtifactsListPanel(BlackboardArtifact.ARTIFACT_TYPE artifactType) {
+ tableModel = new DomainArtifactTableModel(artifactType);
+ initComponents();
+ jTable1.getRowSorter().toggleSortOrder(0);
+ jTable1.getRowSorter().toggleSortOrder(0);
+ }
+
+ /**
+ * Add a listener to the table of artifacts to perform actions when an
+ * artifact is selected.
+ *
+ * @param listener The listener to add to the table of artifacts.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void addSelectionListener(ListSelectionListener listener) {
+ jTable1.getSelectionModel().addListSelectionListener(listener);
+ }
+
+ /**
+ * Remove a listener from the table of artifacts.
+ *
+ * @param listener The listener to remove from the table of artifacts.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void removeListSelectionListener(ListSelectionListener listener) {
+ jTable1.getSelectionModel().removeListSelectionListener(listener);
+ }
+
+ /**
+ * The artifact which is currently selected, null if no artifact is
+ * selected.
+ *
+ * @return The currently selected BlackboardArtifact or null if none is
+ * selected.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ BlackboardArtifact getSelectedArtifact() {
+ int selectedIndex = jTable1.getSelectionModel().getLeadSelectionIndex();
+ if (selectedIndex < jTable1.getSelectionModel().getMinSelectionIndex() || jTable1.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > jTable1.getSelectionModel().getMaxSelectionIndex()) {
+ return null;
+ }
+ return tableModel.getArtifactByRow(jTable1.convertRowIndexToModel(selectedIndex));
+ }
+
+ /**
+ * Whether the list of artifacts is empty.
+ *
+ * @return true if the list of artifacts is empty, false if there are
+ * artifacts.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ boolean isEmpty() {
+ return tableModel.getRowCount() <= 0;
+ }
+
+ /**
+ * Select the first available artifact in the list if it is not empty to
+ * populate the panel to the right.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void selectFirst() {
+ if (!isEmpty()) {
+ jTable1.setRowSelectionInterval(0, 0);
+ } else {
+ jTable1.clearSelection();
+ }
+ }
+
+ /**
+ * Add the specified list of artifacts to the list of artifacts which should
+ * be displayed.
+ *
+ * @param artifactList The list of artifacts to display.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void addArtifacts(List artifactList) {
+ tableModel.setContents(artifactList);
+ jTable1.validate();
+ jTable1.repaint();
+ tableModel.fireTableDataChanged();
+ }
+
+ /**
+ * Remove all artifacts from the list of artifacts displayed.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void clearArtifacts() {
+ tableModel.setContents(new ArrayList<>());
+ tableModel.fireTableDataChanged();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
+ jTable1 = new javax.swing.JTable();
+
+ setOpaque(false);
+
+ jScrollPane1.setBorder(null);
+
+ jTable1.setAutoCreateRowSorter(true);
+ jTable1.setModel(tableModel);
+ jTable1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ jScrollPane1.setViewportView(jTable1);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 607, Short.MAX_VALUE)
+ );
+ }// //GEN-END:initComponents
+
+ /**
+ * Table model which allows the artifact table in this panel to mimic a list
+ * of artifacts.
+ */
+ private class DomainArtifactTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+ private final List artifactList = new ArrayList<>();
+ private final BlackboardArtifact.ARTIFACT_TYPE artifactType;
+
+ /**
+ * Construct a new DomainArtifactTableModel.
+ *
+ * @param artifactType The type of artifact displayed in this table.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ DomainArtifactTableModel(BlackboardArtifact.ARTIFACT_TYPE artifactType) {
+ this.artifactType = artifactType;
+ }
+
+ /**
+ * Set the list of artifacts which should be represented by this table
+ * model.
+ *
+ * @param artifacts The list of BlackboardArtifacts to represent.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void setContents(List artifacts) {
+ jTable1.clearSelection();
+ artifactList.clear();
+ artifactList.addAll(artifacts);
+ }
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ @Override
+ public int getRowCount() {
+ return artifactList.size();
+ }
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ @Override
+ public int getColumnCount() {
+ if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE) {
+ return 3;
+ } else {
+ return 2;
+ }
+ }
+
+ /**
+ * Get the BlackboardArtifact at the specified row.
+ *
+ * @param rowIndex The row the artifact to return is at.
+ *
+ * @return The BlackboardArtifact at the specified row.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ BlackboardArtifact getArtifactByRow(int rowIndex) {
+ return artifactList.get(rowIndex);
+ }
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ @NbBundle.Messages({"ArtifactsListPanel.value.noValue=No value available."})
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ if (columnIndex < 2 || artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE) {
+ try {
+ for (BlackboardAttribute bba : getArtifactByRow(rowIndex).getAttributes()) {
+ if (!StringUtils.isBlank(bba.getDisplayString())) {
+ String stringFromAttribute = getStringForColumn(bba, columnIndex);
+ if (!StringUtils.isBlank(stringFromAttribute)) {
+ return stringFromAttribute;
+ }
+ }
+ }
+ return getFallbackValue(rowIndex, columnIndex);
+ } catch (TskCoreException ex) {
+ logger.log(Level.WARNING, "Error getting attributes for artifact " + getArtifactByRow(rowIndex).getArtifactID(), ex);
+ }
+ }
+ return Bundle.ArtifactsListPanel_value_noValue();
+ }
+
+ /**
+ * Get the appropriate String for the specified column from the
+ * BlackboardAttribute.
+ *
+ * @param bba The BlackboardAttribute which may contain a value.
+ * @param columnIndex The column the value will be displayed in.
+ *
+ * @return The value from the specified attribute which should be
+ * displayed in the specified column, null if the specified
+ * attribute does not contain a value for that column.
+ *
+ * @throws TskCoreException When unable to get abstract files based on
+ * the TSK_PATH_ID.
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ private String getStringForColumn(BlackboardAttribute bba, int columnIndex) throws TskCoreException {
+ if (columnIndex == 0 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()) {
+ return bba.getDisplayString();
+ } else if (columnIndex == 1) {
+ if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD || artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE) {
+ if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()) {
+ return Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(bba.getValueLong()).getName();
+ } else if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID()) {
+ return FilenameUtils.getName(bba.getDisplayString());
+ }
+ } else if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE.getTypeID()) {
+ return bba.getDisplayString();
+ }
+ } else if (columnIndex == 2 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()) {
+ return Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(bba.getValueLong()).getMIMEType();
+ }
+ return null;
+ }
+
+ /**
+ * Private helper method to use when the value we want for either date
+ * or title is not available.
+ *
+ *
+ * @param rowIndex The row the artifact to return is at.
+ * @param columnIndex The column index the attribute will be displayed
+ * at.
+ *
+ * @return A string that can be used in place of the accessed date time
+ * attribute title when they are not available.
+ *
+ * @throws TskCoreException
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ private String getFallbackValue(int rowIndex, int columnIndex) throws TskCoreException {
+ for (BlackboardAttribute bba : getArtifactByRow(rowIndex).getAttributes()) {
+ if (columnIndex == 0 && bba.getAttributeType().getTypeName().startsWith("TSK_DATETIME") && !StringUtils.isBlank(bba.getDisplayString())) {
+ return bba.getDisplayString();
+ } else if (columnIndex == 1 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL.getTypeID() && !StringUtils.isBlank(bba.getDisplayString())) {
+ return bba.getDisplayString();
+ }
+ }
+ return Bundle.ArtifactsListPanel_value_noValue();
+ }
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ @NbBundle.Messages({"ArtifactsListPanel.titleColumn.name=Title",
+ "ArtifactsListPanel.fileNameColumn.name=Name",
+ "ArtifactsListPanel.dateColumn.name=Date/Time",
+ "ArtifactsListPanel.mimeTypeColumn.name=MIME Type"})
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return Bundle.ArtifactsListPanel_dateColumn_name();
+ case 1:
+ if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE || artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD) {
+ return Bundle.ArtifactsListPanel_fileNameColumn_name();
+ } else {
+ return Bundle.ArtifactsListPanel_titleColumn_name();
+ }
+ case 2:
+ return Bundle.ArtifactsListPanel_mimeTypeColumn_name();
+ default:
+ return "";
+ }
+ }
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTable jTable1;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java
new file mode 100644
index 0000000000..9a47e164cf
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java
@@ -0,0 +1,80 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import javax.swing.SwingWorker;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils;
+import org.sleuthkit.autopsy.discovery.search.DomainSearch;
+import org.sleuthkit.autopsy.discovery.search.DomainSearchArtifactsRequest;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+
+/**
+ * SwingWorker to retrieve a list of artifacts for a specified type and domain.
+ */
+class ArtifactsWorker extends SwingWorker, Void> {
+
+ private final BlackboardArtifact.ARTIFACT_TYPE artifactType;
+ private final static Logger logger = Logger.getLogger(ArtifactsWorker.class.getName());
+ private final String domain;
+
+ /**
+ * Construct a new ArtifactsWorker.
+ *
+ * @param artifactType The type of artifact being retrieved.
+ * @param domain The domain the artifacts should have as an attribute.
+ */
+ ArtifactsWorker(BlackboardArtifact.ARTIFACT_TYPE artifactType, String domain) {
+ this.artifactType = artifactType;
+ this.domain = domain;
+ }
+
+ @Override
+ protected List doInBackground() throws Exception {
+ if (artifactType != null && !StringUtils.isBlank(domain)) {
+ DomainSearch domainSearch = new DomainSearch();
+ return domainSearch.getArtifacts(new DomainSearchArtifactsRequest(Case.getCurrentCase().getSleuthkitCase(), domain, artifactType));
+ }
+ return new ArrayList<>();
+ }
+
+ @Override
+ protected void done() {
+ List listOfArtifacts = new ArrayList<>();
+ if (!isCancelled()) {
+ try {
+ listOfArtifacts.addAll(get());
+ } 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);
+ } catch (CancellationException ignored) {
+ //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));
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties
index 5fc4ff56e6..851045c71a 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties
@@ -51,10 +51,12 @@ HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
ObjectDetectedFilterPanel.text=Object Detected:
-DetailsPanel.instancesList.border.title=Instances
DateFilterPanel.mostRecentRadioButton.text=Only last:
DateFilterPanel.dateFilterCheckBox.text=Date Filter:
DomainSummaryPanel.activityLabel.text=
DomainSummaryPanel.pagesLabel.text=
DomainSummaryPanel.filesDownloadedLabel.text=
DomainSummaryPanel.totalVisitsLabel.text=
+FileDetailsPanel.instancesList.border.title=Instances
+CookieDetailsPanel.jLabel1.text=Artifact:
+CookieDetailsPanel.jLabel2.text=
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED
index 7c2c5e6757..a5305503b5 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED
@@ -1,3 +1,8 @@
+ArtifactsListPanel.dateColumn.name=Date/Time
+ArtifactsListPanel.fileNameColumn.name=Name
+ArtifactsListPanel.mimeTypeColumn.name=MIME Type
+ArtifactsListPanel.titleColumn.name=Title
+ArtifactsListPanel.value.noValue=No value available.
ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected.
CTL_OpenDiscoveryAction=Discovery
DataSourceFilterPanel.error.text=At least one data source must be selected.
@@ -126,13 +131,15 @@ HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
ObjectDetectedFilterPanel.text=Object Detected:
-DetailsPanel.instancesList.border.title=Instances
DateFilterPanel.mostRecentRadioButton.text=Only last:
DateFilterPanel.dateFilterCheckBox.text=Date Filter:
DomainSummaryPanel.activityLabel.text=
DomainSummaryPanel.pagesLabel.text=
DomainSummaryPanel.filesDownloadedLabel.text=
DomainSummaryPanel.totalVisitsLabel.text=
+FileDetailsPanel.instancesList.border.title=Instances
+CookieDetailsPanel.jLabel1.text=Artifact:
+CookieDetailsPanel.jLabel2.text=
VideoThumbnailPanel.bytes.text=bytes
VideoThumbnailPanel.deleted.text=All instances of file are deleted.
VideoThumbnailPanel.gigaBytes.text=GB
@@ -144,3 +151,7 @@ VideoThumbnailPanel.nameLabel.more.text=\ and {0} more
# {1} - units
VideoThumbnailPanel.sizeLabel.text=Size: {0} {1}
VideoThumbnailPanel.terraBytes.text=TB
+WebHistoryDetailsPanel.details.attrHeader=Attributes
+WebHistoryDetailsPanel.details.dataSource=Data Source
+WebHistoryDetailsPanel.details.file=File
+WebHistoryDetailsPanel.details.sourceHeader=Source
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form
new file mode 100644
index 0000000000..2c7924e2a4
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form
@@ -0,0 +1,18 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java
new file mode 100644
index 0000000000..81e4fcaddc
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java
@@ -0,0 +1,69 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.discovery.ui;
+
+import org.openide.nodes.Node;
+import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+
+/**
+ * Details panel for displaying the collection of content viewers.
+ */
+final class ContentViewerDetailsPanel extends AbstractArtifactDetailsPanel {
+
+ private static final long serialVersionUID = 1L;
+ private final DataContentPanel contentViewer = DataContentPanel.createInstance();
+
+ /**
+ * Creates new form ContentViewerDetailsPanel
+ */
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ ContentViewerDetailsPanel() {
+ initComponents();
+ add(contentViewer);
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ setLayout(new java.awt.BorderLayout());
+ }// //GEN-END:initComponents
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ @Override
+ public void setArtifact(BlackboardArtifact artifact) {
+ Node node = Node.EMPTY;
+ if (artifact != null) {
+ node = new BlackboardArtifactNode(artifact);
+ }
+ contentViewer.setNode(node);
+ }
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java
index ab54df4341..065c989ddb 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java
@@ -30,6 +30,7 @@ import javax.swing.JList;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@@ -45,6 +46,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
/**
* Creates new form DataSourceFilterPanel.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
DataSourceFilterPanel() {
initComponents();
setUpDataSourceFilter();
@@ -109,6 +111,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
private javax.swing.JScrollPane dataSourceScrollPane;
// End of variables declaration//GEN-END:variables
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
void configurePanel(boolean selected, int[] indicesSelected) {
dataSourceCheckbox.setSelected(selected);
@@ -124,6 +127,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
JCheckBox getCheckbox() {
return dataSourceCheckbox;
@@ -137,6 +141,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
/**
* Initialize the data source filter.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void setUpDataSourceFilter() {
int count = 0;
try {
@@ -156,6 +161,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
JList> getList() {
return dataSourceList;
@@ -193,6 +199,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@NbBundle.Messages({"DataSourceFilterPanel.error.text=At least one data source must be selected."})
@Override
String checkForError() {
@@ -202,6 +209,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
return "";
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
AbstractFilter getFilter() {
if (dataSourceCheckbox.isSelected()) {
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java
index 528a5d1bc2..2fbe6610ff 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java
@@ -33,6 +33,7 @@ import javax.swing.JSpinner;
import javax.swing.event.ListSelectionListener;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.communications.Utils;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering;
/**
@@ -48,6 +49,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
*/
@NbBundle.Messages({"# {0} - timeZone",
"DateFilterPanel.dateRange.text=Date Range ({0}):"})
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
DateFilterPanel() {
initComponents();
rangeRadioButton.setText(Bundle.DateFilterPanel_dateRange_text(Utils.getUserPreferredZoneId().toString()));
@@ -225,6 +227,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
endCheckBox.firePropertyChange("EndButtonChange", true, false);
}//GEN-LAST:event_rangeRadioButtonStateChanged
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
void configurePanel(boolean selected, int[] indicesSelected) {
dateFilterCheckBox.setSelected(selected);
@@ -238,6 +241,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
JCheckBox getCheckbox() {
return dateFilterCheckBox;
@@ -253,6 +257,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
return null;
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
void addListeners(ActionListener actionListener, ListSelectionListener listListener) {
dateFilterCheckBox.addActionListener(actionListener);
@@ -274,6 +279,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
});
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
void removeListeners() {
for (ActionListener listener : dateFilterCheckBox.getActionListeners()) {
@@ -302,6 +308,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@NbBundle.Messages({"DateFilterPanel.invalidRange.text=Range or Only Last must be selected.",
"DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter.",
"DateFilterPanel.startAfterEnd.text=Start date should be before the end date when both are enabled."})
@@ -320,6 +327,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
return "";
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
AbstractFilter getFilter() {
if (dateFilterCheckBox.isSelected()) {
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java
index f2f42bd113..565dad8a88 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java
@@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes;
import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils;
import org.sleuthkit.autopsy.discovery.search.Group;
@@ -99,6 +100,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Private constructor to construct a new DiscoveryDialog
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Messages("DiscoveryDialog.name.text=Discovery")
private DiscoveryDialog() {
super(WindowManager.getDefault().getMainWindow(), Bundle.DiscoveryDialog_name_text(), true);
@@ -151,6 +153,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Update the search settings to a default state.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
void updateSearchSettings() {
removeAllPanels();
imageFilterPanel = null;
@@ -176,6 +179,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Set the type buttons to a default state where none are selected.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void unselectAllButtons() {
imagesButton.setSelected(false);
imagesButton.setEnabled(true);
@@ -194,6 +198,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Private helper method to perform update of comboboxes update.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void updateComboBoxes() {
// Set up the grouping attributes
List groupingAttrs = new ArrayList<>();
@@ -230,6 +235,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
*
* @return The panel that corresponds to the currently selected type.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private AbstractFiltersPanel getSelectedFilterPanel() {
switch (type) {
case IMAGE:
@@ -251,6 +257,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
*
* @param type The Type of GroupingAttribute to add.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void addTypeToGroupByComboBox(GroupingAttributeType type) {
switch (type) {
case FREQUENCY:
@@ -282,7 +289,8 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Validate the filter settings for File type filters.
*/
- synchronized void validateDialog() {
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ void validateDialog() {
AbstractFiltersPanel panel = getSelectedFilterPanel();
if (panel != null) {
panel.validateFields();
@@ -551,6 +559,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Helper method to remove all filter panels and their listeners
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void removeAllPanels() {
if (imageFilterPanel != null) {
remove(imageFilterPanel);
@@ -635,6 +644,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
repaint();
}//GEN-LAST:event_domainsButtonActionPerformed
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
public void dispose() {
setVisible(false);
@@ -643,6 +653,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
/**
* Cancel the searchWorker if it exists.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
void cancelSearch() {
if (searchWorker != null) {
searchWorker.cancel(true);
@@ -656,6 +667,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
* @param error The error message to display, empty string if there is no
* error.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void setValid(String error) {
if (StringUtils.isBlank(error)) {
errorLabel.setText("");
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java
index dce68e3a41..229c7510e3 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java
@@ -27,6 +27,7 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.datamodel.AbstractFile;
@@ -42,28 +43,27 @@ class DiscoveryThumbnailChildren extends Children.Keys {
/*
* Creates the list of thumbnails from the given list of AbstractFiles.
*/
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
DiscoveryThumbnailChildren(List files) {
super(false);
-
this.files = files;
-
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
protected Node[] createNodes(AbstractFile t) {
return new Node[]{new ThumbnailNode(t)};
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
protected void addNotify() {
super.addNotify();
-
Set thumbnails = new TreeSet<>((AbstractFile file1, AbstractFile file2) -> {
int result = Long.compare(file1.getSize(), file2.getSize());
if (result == 0) {
result = file1.getName().compareTo(file2.getName());
}
-
return result;
});
thumbnails.addAll(files);
@@ -75,10 +75,12 @@ class DiscoveryThumbnailChildren extends Children.Keys {
*/
static class ThumbnailNode extends FileNode {
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
ThumbnailNode(AbstractFile file) {
super(file, false);
}
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form
index 54630599ec..c84525148c 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form
@@ -1,6 +1,6 @@
-