diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties
index 358971eaa8..6665c2e958 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties
@@ -38,3 +38,4 @@ VisualizationPanel.organicLayoutButton.text=Organic
VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic
VisualizationPanel.hierarchyLayoutButton.text=Hierarchical
VisualizationPanel.clearVizButton.text_1=Clear Viz.
+VisualizationPanel.snapshotButton.text_1=Snapshot Report
diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED
index 381ec7b337..e4daded09b 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED
@@ -32,6 +32,23 @@ ResetAndPinAccountsAction.singularText=Visualize Only Selected Account
UnpinAccountsAction.pluralText=Remove Selected Accounts
UnpinAccountsAction.singularText=Remove Selected Account
VisalizationPanel.paintingError=Problem painting visualization.
+# {0} - default name
+VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}
+VisualizationPane_blank_report_title=Blank Report Name
+VisualizationPane_DisplayName=Open Report
+VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:
+VisualizationPane_MessageBoxTitle=Open Report Failure
+VisualizationPane_MissingReportFileMessage=The report file no longer exists.
+VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.
+VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.
+VisualizationPane_Open_Report=Open Report
+# {0} - report name
+VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}
+VisualizationPane_Report_OK_Button=OK
+# {0} - report path
+VisualizationPane_Report_Success=Report Successfully create at:\n{0}
+VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.
+VisualizationPane_reportName=Communications Snapshot
VisualizationPanel.cancelButton.text=Cancel
VisualizationPanel.computingLayout=Computing Layout
VisualizationPanel.jButton1.text=Fast Organic
@@ -65,3 +82,8 @@ VisualizationPanel.organicLayoutButton.text=Organic
VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic
VisualizationPanel.hierarchyLayoutButton.text=Hierarchical
VisualizationPanel.clearVizButton.text_1=Clear Viz.
+VisualizationPanel.snapshotButton.text_1=Snapshot Report
+VisualizationPanel_action_dialogs_title=Communications
+VisualizationPanel_action_name_text=Snapshot Report
+VisualizationPanel_module_name=Communications
+VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation.
diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form
index 49b86ae014..4e0a73a6c7 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form
@@ -11,7 +11,7 @@
-
+
@@ -49,9 +49,9 @@
-
+
-
+
@@ -120,6 +120,10 @@
+
+
+
+
@@ -143,6 +147,8 @@
+
+
@@ -310,6 +316,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
index 8f5e6e5c94..f3d4f95bb2 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
@@ -28,6 +28,7 @@ import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxICell;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent;
+import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
@@ -41,19 +42,28 @@ import com.mxgraph.view.mxGraphView;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
+import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
+import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
+import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
+import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -75,14 +85,17 @@ import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
+import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
+import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.Notifications;
import org.jdesktop.layout.GroupLayout;
import org.jdesktop.layout.LayoutStyle;
@@ -92,8 +105,11 @@ import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ProxyLookup;
+import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.communications.snapshot.CommSnapShotReportWriter;
+import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
@@ -101,7 +117,6 @@ import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
-
/**
* A panel that goes in the Visualize tab of the Communications Visualization
* Tool. Hosts an JGraphX mxGraphComponent that implements the communications
@@ -172,7 +187,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
public void paint(Graphics graphics) {
try {
super.paint(graphics);
- } catch (NullPointerException ex) { //NOPMD
+ } catch (NullPointerException ex) { //NOPMD
/* We can't find the underlying cause of the NPE in
* jgraphx, but it doesn't seem to cause any
* noticeable problems, so we are just logging it
@@ -387,6 +402,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
zoomLabel = new JLabel();
clearVizButton = new JButton();
jSeparator2 = new JToolBar.Separator();
+ snapshotButton = new JButton();
+ jSeparator3 = new JToolBar.Separator();
notificationsJFXPanel = new JFXPanel();
setLayout(new BorderLayout());
@@ -406,9 +423,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
placeHolderPanel.setLayout(placeHolderPanelLayout);
placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING)
.add(placeHolderPanelLayout.createSequentialGroup()
- .addContainerGap(71, Short.MAX_VALUE)
+ .addContainerGap(268, Short.MAX_VALUE)
.add(jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE)
- .addContainerGap(248, Short.MAX_VALUE))
+ .addContainerGap(445, Short.MAX_VALUE))
);
placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING)
.add(placeHolderPanelLayout.createSequentialGroup()
@@ -505,6 +522,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
jSeparator2.setOrientation(SwingConstants.VERTICAL);
+ snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N
+ snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N
+ snapshotButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent evt) {
+ snapshotButtonActionPerformed(evt);
+ }
+ });
+
+ jSeparator3.setOrientation(SwingConstants.VERTICAL);
+
GroupLayout toolbarLayout = new GroupLayout(toolbar);
toolbar.setLayout(toolbarLayout);
toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING)
@@ -537,6 +564,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
.add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.RELATED)
.add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(LayoutStyle.RELATED)
+ .add(jSeparator3, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(LayoutStyle.RELATED)
+ .add(snapshotButton)
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING)
@@ -556,7 +587,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
.add(jLabel2)
.add(zoomLabel)
.add(clearVizButton)
- .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(snapshotButton)
+ .add(jSeparator3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.add(3, 3, 3))
);
@@ -648,6 +681,24 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
setCursor(Cursor.getDefaultCursor());
}//GEN-LAST:event_clearVizButtonActionPerformed
+ @NbBundle.Messages({
+ "VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation."
+ })
+ private void snapshotButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_snapshotButtonActionPerformed
+ try {
+ handleSnapshotEvent();
+ } catch (NoCurrentCaseException | IOException ex) {
+ logger.log(Level.SEVERE, "Unable to create communications snapsot report", ex); //NON-NLS
+
+ Platform.runLater(()
+ -> Notifications.create().owner(notificationsJFXPanel.getScene().getWindow())
+ .text(Bundle.VisualizationPanel_snapshot_report_failure())
+ .showWarning());
+ } catch( TskCoreException ex) {
+ logger.log(Level.WARNING, "Unable to add report to currenct case", ex); //NON-NLS
+ }
+ }//GEN-LAST:event_snapshotButtonActionPerformed
+
private void fitGraph() {
graphComponent.zoomTo(1, true);
mxPoint translate = graph.getView().getTranslate();
@@ -674,7 +725,129 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
graphComponent.zoom((heightFactor + widthFactor) / 2.0);
}
+
+ /**
+ * Handle the ActionPerformed event from the Snapshot button.
+ *
+ * @throws NoCurrentCaseException
+ * @throws IOException
+ */
+ @NbBundle.Messages({
+ "VisualizationPanel_action_dialogs_title=Communications",
+ "VisualizationPanel_module_name=Communications",
+ "VisualizationPanel_action_name_text=Snapshot Report",
+ "VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:",
+ "VisualizationPane_reportName=Communications Snapshot",
+ "# {0} - default name",
+ "VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}",
+ "VisualizationPane_blank_report_title=Blank Report Name",
+ "# {0} - report name",
+ "VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}"
+ })
+ private void handleSnapshotEvent() throws NoCurrentCaseException, IOException, TskCoreException {
+ Case currentCase = Case.getCurrentCaseThrows();
+ Date generationDate = new Date();
+ final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MMddyyyyHHmmss").format(generationDate)); //NON_NLS
+
+ final JTextField text = new JTextField(50);
+ final JPanel panel = new JPanel(new GridLayout(2, 1));
+ panel.add(new JLabel(Bundle.VisualizationPane_fileName_prompt()));
+ panel.add(text);
+
+ text.setText(defaultReportName);
+
+ int result = JOptionPane.showConfirmDialog(graphComponent, panel,
+ Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
+
+ if (result == JOptionPane.OK_OPTION) {
+ String enteredReportName = text.getText();
+
+ if(enteredReportName.trim().isEmpty()){
+ result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_accept_defaultName(defaultReportName), Bundle.VisualizationPane_blank_report_title(), JOptionPane.OK_CANCEL_OPTION);
+ if(result != JOptionPane.OK_OPTION) {
+ return;
+ }
+ }
+
+ String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName);
+ Path reportPath = Paths.get(currentCase.getReportDirectory(), reportName);
+ if (Files.exists(reportPath)) {
+ result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_overrite_exiting(reportName),
+ Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
+
+ if (result == JOptionPane.OK_OPTION) {
+ FileUtil.deleteFileDir(reportPath.toFile());
+ createReport(currentCase, reportName);
+ }
+ } else {
+ createReport(currentCase, reportName);
+ currentCase.addReport(reportPath.toString(), Bundle.VisualizationPanel_module_name(), reportName);
+
+ }
+ }
+ }
+
+ /**
+ * Create the Snapshot Report.
+ *
+ * @param currentCase The current case
+ * @param reportName User selected name for the report
+ *
+ * @throws IOException
+ */
+ @NbBundle.Messages({
+ "VisualizationPane_DisplayName=Open Report",
+ "VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.",
+ "VisualizationPane_MessageBoxTitle=Open Report Failure",
+ "VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.",
+ "VisualizationPane_MissingReportFileMessage=The report file no longer exists.",
+ "VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.",
+ "# {0} - report path",
+ "VisualizationPane_Report_Success=Report Successfully create at:\n{0}",
+ "VisualizationPane_Report_OK_Button=OK",
+ "VisualizationPane_Open_Report=Open Report",})
+ private void createReport(Case currentCase, String reportName) throws IOException {
+
+ // Create the report.
+ Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, Bundle.VisualizationPane_reportName()); //NON_NLS
+ BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, graph.getView().getScale(), Color.WHITE, true, null);
+ Path reportPath = new CommSnapShotReportWriter(currentCase, reportFolderPath, reportName, new Date(), image, currentFilter).writeReport();
+
+ // Report success to the user and offer to open the report.
+ String message = Bundle.VisualizationPane_Report_Success(reportPath.toAbsolutePath());
+ String[] buttons = {Bundle.VisualizationPane_Open_Report(), Bundle.VisualizationPane_Report_OK_Button()};
+
+ int result = JOptionPane.showOptionDialog(graphComponent, message,
+ Bundle.VisualizationPanel_action_dialogs_title(),
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE,
+ null, buttons, buttons[1]);
+ if (result == JOptionPane.YES_NO_OPTION) {
+ try {
+ Desktop.getDesktop().open(reportPath.toFile());
+ } catch (IOException ex) {
+ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
+ Bundle.VisualizationPane_NoAssociatedEditorMessage(),
+ Bundle.VisualizationPane_MessageBoxTitle(),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (UnsupportedOperationException ex) {
+ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
+ Bundle.VisualizationPane_NoOpenInEditorSupportMessage(),
+ Bundle.VisualizationPane_MessageBoxTitle(),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (IllegalArgumentException ex) {
+ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
+ Bundle.VisualizationPane_MissingReportFileMessage(),
+ Bundle.VisualizationPane_MessageBoxTitle(),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (SecurityException ex) {
+ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
+ Bundle.VisualizationPane_ReportFileOpenPermissionDeniedMessage(),
+ Bundle.VisualizationPane_MessageBoxTitle(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
// Variables declaration - do not modify//GEN-BEGIN:variables
private JPanel borderLayoutPanel;
@@ -687,10 +860,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private JLabel jLabel2;
private JToolBar.Separator jSeparator1;
private JToolBar.Separator jSeparator2;
+ private JToolBar.Separator jSeparator3;
private JTextArea jTextArea1;
private JFXPanel notificationsJFXPanel;
private JButton organicLayoutButton;
private JPanel placeHolderPanel;
+ private JButton snapshotButton;
private JSplitPane splitPane;
private JPanel toolbar;
private JButton zoomActualButton;
@@ -1001,4 +1176,4 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
lockedVertexModel.lock(selectedVertices);
}
}
-}
+}
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java
new file mode 100755
index 0000000000..7b4dee6d19
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java
@@ -0,0 +1,175 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 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.communications.snapshot;
+
+import java.util.List;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Set;
+import javax.imageio.ImageIO;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.report.uisnapshot.UiSnapShotReportWriter;
+import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.CommunicationsFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.SubFilter;
+import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Generate and write the Communication snapshot report to disk.
+ */
+public class CommSnapShotReportWriter extends UiSnapShotReportWriter {
+
+ private final BufferedImage image;
+ private final CommunicationsFilter filter;
+
+ /**
+ * Constructor
+ *
+ * @param currentCase The Case to write a report for.
+ * @param reportFolderPath The Path to the folder that will contain the
+ * report.
+ * @param reportName The name of the report.
+ * @param generationDate The generation Date of the report.
+ * @param snapshot A snapshot of the view to include in the report.
+ */
+ public CommSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate, BufferedImage snapshot, CommunicationsFilter filter) {
+
+ super(currentCase, reportFolderPath, reportName, generationDate);
+
+ this.image = snapshot;
+ this.filter = filter;
+
+ }
+
+ /**
+ * Generate and write the html page that shows the snapshot and the state of
+ * the CommunicationFilters
+ *
+ * @throws IOException If there is a problem writing the html file to disk.
+ */
+ @Override
+ protected void writeSnapShotHTMLFile() throws IOException {
+ SimpleDateFormat formatter = new SimpleDateFormat("MMMMM dd, yyyy"); //NON-NLS
+
+ ImageIO.write(image, "png", getReportFolderPath().resolve("snapshot.png").toFile()); //NON-NLS
+
+ //make a map of context objects to resolve template paramaters against
+ HashMap snapShotContext = new HashMap<>();
+ snapShotContext.put("reportTitle", getReportName()); //NON-NLS
+
+ List filters = filter.getAndFilters();
+
+ for (SubFilter filter : filters) {
+ if (filter instanceof DateRangeFilter) {
+ long startDate = ((DateRangeFilter) filter).getStartDate();
+ long endDate = ((DateRangeFilter) filter).getEndDate();
+
+ if (startDate > 0) {
+
+ snapShotContext.put("startTime", formatter.format(new Date((Instant.ofEpochSecond(startDate)).toEpochMilli()))); //NON-NLS
+ }
+
+ if (endDate > 0) {
+ snapShotContext.put("endTime", formatter.format(new Date((Instant.ofEpochSecond(endDate)).toEpochMilli()))); //NON-NLS
+ }
+ } else if (filter instanceof AccountTypeFilter) {
+
+ Set selectedAccounts = ((AccountTypeFilter) filter).getAccountTypes();
+ ArrayList fullAccountList = new ArrayList<>();
+ for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) {
+ if (type == Account.Type.CREDIT_CARD) {
+ continue;
+ }
+
+ fullAccountList.add(new ReportWriterHelper(type.getDisplayName(), selectedAccounts.contains(type)));
+ }
+
+ snapShotContext.put("accounts", fullAccountList);
+ } else if (filter instanceof DeviceFilter) {
+ Collection ids = ((DeviceFilter) filter).getDevices();
+ ArrayList list = new ArrayList<>();
+ try {
+ final SleuthkitCase sleuthkitCase = getCurrentCase().getSleuthkitCase();
+ for (DataSource dataSource : sleuthkitCase.getDataSources()) {
+ boolean selected = ids.contains(dataSource.getDeviceId());
+ String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
+ list.add(new ReportWriterHelper(dsName, selected));
+ }
+ } catch (TskCoreException ex) {
+
+ }
+
+ snapShotContext.put("devices", list);
+ }
+ }
+
+ fillTemplateAndWrite("/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html", "Snapshot", snapShotContext, getReportFolderPath().resolve("snapshot.html")); //NON-NLS
+ }
+
+ /**
+ * Helper class for use with the html template
+ */
+ private final class ReportWriterHelper {
+
+ private final String label;
+ private final boolean selected;
+
+ /**
+ * Helper class for use with the html template.
+ *
+ * @param label Display label
+ * @param selected Boolean selected state
+ */
+ ReportWriterHelper(String label, boolean selected) {
+ this.label = label;
+ this.selected = selected;
+ }
+
+ /**
+ * Returns the display label
+ *
+ * @return The display label
+ */
+ public String getLabel(){
+ return label;
+ }
+
+ /**
+ * Returns the selection state
+ *
+ * @return The selection state
+ */
+ public boolean isSelected(){
+ return selected;
+ }
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html b/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html
new file mode 100755
index 0000000000..078f6fc978
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html
@@ -0,0 +1,21 @@
+
+
+ Communications Snapshot: {{reportTitle}}
+
+
+
+
Communications Snapshot
+
+
+
+
Date Range
+
Start:
{{startTime}}
+
End:
{{endTime}}
+
Devices:
+ {{#devices}}
{{label}}
{{/devices}}
+
Account Types
+ {{#accounts}}
{{label}}
{{/accounts}}
+
+
+
+
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/report/images/image.png b/Core/src/org/sleuthkit/autopsy/report/images/image.png
new file mode 100755
index 0000000000..fc3c393caa
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/report/images/image.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/report/uisnapshot/UiSnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/UiSnapShotReportWriter.java
new file mode 100755
index 0000000000..1017e2e743
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/UiSnapShotReportWriter.java
@@ -0,0 +1,241 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2016 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.report.uisnapshot;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.report.ReportBranding;
+
+/**
+ * Generate and write the snapshot report to disk.
+ */
+public abstract class UiSnapShotReportWriter {
+
+ /**
+ * mustache.java template factory.
+ */
+ private final static MustacheFactory mf = new DefaultMustacheFactory();
+
+ private final Case currentCase;
+ private final Path reportFolderPath;
+ private final String reportName;
+ private final ReportBranding reportBranding;
+
+ private Date generationDate;
+
+ /**
+ * Constructor
+ *
+ * @param currentCase The Case to write a report for.
+ * @param reportFolderPath The Path to the folder that will contain the
+ * report.
+ * @param reportName The name of the report.
+ * @param generationDate The generation Date of the report.
+ * @param snapshot A snapshot of the view to include in the
+ * report.
+ */
+ protected UiSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate) {
+ this.currentCase = currentCase;
+ this.reportFolderPath = reportFolderPath;
+ this.reportName = reportName;
+ this.generationDate = generationDate;
+
+ this.reportBranding = new ReportBranding();
+ }
+
+ /**
+ * Generate and write the report to disk.
+ *
+ * @return The Path to the "main file" of the report. This is the file that
+ * Autopsy shows in the results view when the Reports Node is
+ * selected in the DirectoryTree.
+ *
+ * @throws IOException If there is a problem writing the report.
+ */
+ public Path writeReport() throws IOException {
+ //ensure directory exists
+ Files.createDirectories(reportFolderPath);
+
+ copyResources();
+
+ writeSummaryHTML();
+ writeSnapShotHTMLFile();
+ return writeIndexHTML();
+ }
+
+ /**
+ * Get the name for the report.
+ *
+ * @return Returns the reportName
+ */
+ protected String getReportName() {
+ return reportName;
+ }
+
+ /**
+ * Get the folder path for the report.
+ *
+ * @return Report folder path
+ */
+ protected Path getReportFolderPath() {
+ return reportFolderPath;
+ }
+
+ /**
+ * Get the case for this report.
+ *
+ * @return Current case object
+ */
+ protected Case getCurrentCase() {
+ return currentCase;
+ }
+
+ /**
+ * Generate and write the html page that shows the snapshot and the state of
+ * any filters.
+ *
+ * @throws IOException If there is a problem writing the html file to disk.
+ */
+ protected abstract void writeSnapShotHTMLFile() throws IOException ;
+
+ /**
+ * Generate and write the main html page with frames for navigation on the
+ * left and content on the right.
+ *
+ * @return The Path of the written html file.
+ *
+ * @throws IOException If there is a problem writing the html file to disk.
+ */
+ private Path writeIndexHTML() throws IOException {
+ //make a map of context objects to resolve template paramaters against
+ HashMap indexContext = new HashMap<>();
+ indexContext.put("reportBranding", reportBranding); //NON-NLS
+ indexContext.put("reportName", reportName); //NON-NLS
+ Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS
+
+ fillTemplateAndWrite("/org/sleuthkit/autopsy/report/uisnapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS
+ return reportIndexFile;
+ }
+
+ /**
+ * * Generate and write the summary of the current case for this report.
+ *
+ * @throws IOException If there is a problem writing the html file to disk.
+ */
+ private void writeSummaryHTML() throws IOException {
+ //make a map of context objects to resolve template paramaters against
+ HashMap summaryContext = new HashMap<>();
+ summaryContext.put("reportName", reportName); //NON-NLS
+ summaryContext.put("reportBranding", reportBranding); //NON-NLS
+ summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS
+ summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS
+ summaryContext.put("currentCase", currentCase); //NON-NLS
+ String agencyLogo = "agency_logo.png"; //default name for agency logo.
+ if (StringUtils.isNotBlank(reportBranding.getAgencyLogoPath())){
+ agencyLogo = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString();
+ }
+ summaryContext.put("agencyLogoFileName", agencyLogo);
+ fillTemplateAndWrite("/org/sleuthkit/autopsy/report/uisnapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS
+ }
+
+ /**
+ * Fill in the mustache template at the given location using the values from
+ * the given context object and save it to the given outPutFile.
+ *
+ * @param templateLocation The location of the template. suitible for use
+ * with Class.getResourceAsStream
+ * @param templateName The name of the tempalte. (Used by mustache to
+ * cache templates?)
+ * @param context The contect to use to fill in the template
+ * values.
+ * @param outPutFile The filled in tempalte will be saced at this
+ * Path.
+ *
+ * @throws IOException If there is a problem saving the filled in template
+ * to disk.
+ */
+ protected void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException {
+
+ Mustache summaryMustache = mf.compile(new InputStreamReader(UiSnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName);
+ try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS
+ summaryMustache.execute(writer, context);
+ }
+ }
+
+ /**
+ * Copy static resources (static html, css, images, etc) to the reports
+ * folder.
+ *
+ * @throws IOException If there is a problem copying the resources.
+ */
+ private void copyResources() throws IOException {
+
+ //pull generator and agency logos from branding
+ String generatorLogoPath = reportBranding.getGeneratorLogoPath();
+ if (StringUtils.isNotBlank(generatorLogoPath)) {
+ Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS
+ }
+ String agencyLogoPath = reportBranding.getAgencyLogoPath();
+ if (StringUtils.isNotBlank(agencyLogoPath)) {
+ Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve(Paths.get(reportBranding.getAgencyLogoPath()).getFileName())); //NON-NLS
+ }
+
+ //copy favicon
+ if (StringUtils.isBlank(agencyLogoPath)) {
+ copyInternalResource("/org/sleuthkit/autopsy/report/images/favicon.ico", "favicon.ico");
+ } else {
+ Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("favicon.ico")); //NON-NLS
+ }
+
+ copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/navigation.html", "nav.html");
+ copyInternalResource("/org/sleuthkit/autopsy/report/images/summary.png", "summary.png");
+ copyInternalResource("/org/sleuthkit/autopsy/report/images/image.png", "snapshot_icon.png");
+ copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/index.css", "index.css");
+ copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/summary.css", "summary.css");
+ }
+
+ /**
+ * Copies internal resource to the report folder.
+ *
+ * @param internalPath Location in jar of the image
+ * @param fileName Name to give resource in new location
+ *
+ * @throws IOException
+ */
+ private void copyInternalResource(String internalPath, String fileName) throws IOException{
+ try (InputStream resource = UiSnapShotReportWriter.class.getResourceAsStream(internalPath)) { //NON-NLS
+ Files.copy(resource, reportFolderPath.resolve(fileName)); //NON-NLS
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/index.css
old mode 100644
new mode 100755
similarity index 100%
rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css
rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/index.css
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/index_template.html
old mode 100644
new mode 100755
similarity index 100%
rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html
rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/index_template.html
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/navigation.html
old mode 100644
new mode 100755
similarity index 85%
rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html
rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/navigation.html
index 02b6705cfb..bfcf061e34
--- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html
+++ b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/navigation.html
@@ -9,7 +9,7 @@