diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
index 43ceba39ba..b0e36da986 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
@@ -32,6 +32,7 @@ DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found
DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)
DataResultViewerTable.countRender.name=O
DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository
+DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export
DataResultViewerTable.firstColLbl=Name
DataResultViewerTable.goToPageTextField.err=Invalid page number
# {0} - totalPages
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form
index 87e771695b..6aeda07298 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form
@@ -18,12 +18,11 @@
-
-
+
-
+
-
+
@@ -33,7 +32,8 @@
-
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
index eb5fad97f5..e6c1110038 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
@@ -77,6 +77,7 @@ import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
@@ -176,6 +177,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
initComponents();
initializePagingSupport();
+
+ /*
+ * Disable the CSV export button for the common properties results
+ */
+ if (this instanceof org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributesSearchResultsViewerTable) {
+ exportCSVButton.setEnabled(false);
+ }
/*
* Configure the child OutlineView (explorer view) component.
@@ -1352,12 +1360,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
- .addComponent(exportCSVButton)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addContainerGap()
.addComponent(pageLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGap(14, 14, 14)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
@@ -1367,7 +1374,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
.addComponent(gotoPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap())
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(exportCSVButton))
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
@@ -1407,8 +1415,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
pagingSupport.gotoPage();
}//GEN-LAST:event_gotoPageTextFieldActionPerformed
+ @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
+ })
private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
- org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(rootNode.getChildren().getNodes()), this);
+ Node currentRoot = this.getExplorerManager().getRootContext();
+ if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
+ org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
+ } else {
+ MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
+ }
}//GEN-LAST:event_exportCSVButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED
index 6ec6a0e76b..f70a374bc5 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED
@@ -10,6 +10,7 @@ DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contai
DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source?
DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.
DirectoryTreeTopComponent.resultsView.title=Listing
+ExportCSV.saveNodesToCSV.empty=No data to export
# {0} - Output file
ExportCSV.saveNodesToCSV.fileExists=File {0} already exists
ExportCSV.saveNodesToCSV.noCurrentCase=No open case available
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java
index dce6244084..7fa4560afc 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-2018 Basis Technology Corp.
+ * Copyright 2019 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,7 +22,6 @@ import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.BufferedWriter;
-import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
@@ -31,10 +30,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.AbstractAction;
@@ -48,14 +45,8 @@ import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
-import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
-import org.sleuthkit.autopsy.datamodel.ContentUtils;
-import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor;
-import org.sleuthkit.autopsy.datamodel.FileNode;
-import org.sleuthkit.datamodel.AbstractFile;
-import org.openide.nodes.AbstractNode;
import org.openide.nodes.Node;
import org.openide.nodes.Node.PropertySet;
import org.openide.nodes.Node.Property;
@@ -76,6 +67,12 @@ public final class ExportCSVAction extends AbstractAction {
// node in the array returns a reference to the same action object from Node.getActions(boolean).
private static ExportCSVAction instance;
+ /**
+ * Get an instance of the Action. See above for why
+ * the class is a singleton.
+ *
+ * @return the instance
+ */
public static synchronized ExportCSVAction getInstance() {
if (null == instance) {
instance = new ExportCSVAction();
@@ -104,20 +101,28 @@ public final class ExportCSVAction extends AbstractAction {
saveNodesToCSV(selectedNodes, (Component)e.getSource());
}
+ /**
+ * Save the selected nodes to a CSV file
+ *
+ * @param nodesToExport the nodes to save
+ * @param component
+ */
@NbBundle.Messages({
"# {0} - Output file",
"ExportCSV.saveNodesToCSV.fileExists=File {0} already exists",
- "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available"})
+ "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available",
+ "ExportCSV.saveNodesToCSV.empty=No data to export"})
public static void saveNodesToCSV(Collection extends Node> nodesToExport, Component component) {
if (nodesToExport.isEmpty()) {
+ MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_empty());
return;
}
try {
-
+ // Set up the file chooser with a default name and either the Export
+ // folder or the last used folder.
String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode());
-
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows())));
fileChooser.setSelectedFile(new File(fileName));
@@ -126,15 +131,18 @@ public final class ExportCSVAction extends AbstractAction {
int returnVal = fileChooser.showSaveDialog(component);
if (returnVal == JFileChooser.APPROVE_OPTION) {
+ // Get the file name, appending .csv if necessary
File selectedFile = fileChooser.getSelectedFile();
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
}
+
+ // Save the directory used for next time
updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows());
if (selectedFile.exists()) {
logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS
- MessageNotifyUtil.Message.info(Bundle.ExportCSV_actionPerformed_fileExists(selectedFile));
+ MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_fileExists(selectedFile));
return;
}
@@ -142,7 +150,7 @@ public final class ExportCSVAction extends AbstractAction {
writer.execute();
}
} catch (NoCurrentCaseException ex) {
- JOptionPane.showMessageDialog(component, Bundle.ExportCSV_actionPerformed_noCurrentCase());
+ JOptionPane.showMessageDialog(component, Bundle.ExportCSV_saveNodesToCSV_noCurrentCase());
logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
}
}
@@ -165,13 +173,10 @@ public final class ExportCSVAction extends AbstractAction {
String parentName = prop.getValue().toString();
// Strip off the count (if present)
- System.out.println("parentName (raw) : " + parentName);
parentName = parentName.replaceAll("\\([0-9]+\\)$", "");
- System.out.println("parentName (after paren regex) : " + parentName);
// Strip out any invalid characters
parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_");
- System.out.println("parentName (after char regex) : " + parentName);
return parentName + " " + dateStr;
} catch (IllegalAccessException | InvocationTargetException ex) {
@@ -190,7 +195,7 @@ public final class ExportCSVAction extends AbstractAction {
*
* @return The export directory path.
*/
- private static String getExportDirectory(Case openCase) { // TODO sync
+ private static String getExportDirectory(Case openCase) {
String caseExportPath = openCase.getExportDirectory();
if (userDefinedExportPath == null) {
@@ -306,6 +311,13 @@ public final class ExportCSVAction extends AbstractAction {
return null;
}
+ /**
+ * Convert list of values to a comma separated string.
+ *
+ * @param values Values to convert
+ *
+ * @return values as CSV
+ */
private String listToCSV(List values) {
return "\"" + String.join("\",\"", values) + "\"\n";
}