Autopsy  4.13.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExportCSVAction.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this content except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.directorytree;
20 
21 import java.awt.Component;
22 import java.awt.event.ActionEvent;
23 import java.io.File;
24 import java.io.BufferedWriter;
25 import java.io.FileOutputStream;
26 import java.io.OutputStreamWriter;
27 import java.lang.reflect.InvocationTargetException;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Calendar;
32 import java.util.Collection;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.concurrent.ExecutionException;
36 import java.util.logging.Level;
37 import javax.swing.AbstractAction;
38 import javax.swing.JFileChooser;
39 import javax.swing.JOptionPane;
40 import javax.swing.SwingWorker;
41 import javax.swing.filechooser.FileNameExtensionFilter;
42 import org.netbeans.api.progress.ProgressHandle;
43 import org.openide.util.Cancellable;
44 import org.openide.util.NbBundle;
45 import org.openide.util.Utilities;
51 import org.openide.nodes.Node;
52 import org.openide.nodes.Node.PropertySet;
53 import org.openide.nodes.Node.Property;
54 
58 public final class ExportCSVAction extends AbstractAction {
59 
60  private static final Logger logger = Logger.getLogger(ExportCSVAction.class.getName());
61  private final static String DEFAULT_FILENAME = "Results";
62  private final static List<String> columnsToSkip = Arrays.asList(AbstractFilePropertyType.SCORE.toString(),
64 
65  private static String userDefinedExportPath;
66 
67  // This class is a singleton to support multi-selection of nodes, since
68  // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
69  // node in the array returns a reference to the same action object from Node.getActions(boolean).
70  private static ExportCSVAction instance;
71 
78  public static synchronized ExportCSVAction getInstance() {
79  if (null == instance) {
80  instance = new ExportCSVAction();
81  }
82  return instance;
83  }
84 
88  @NbBundle.Messages({"ExportCSV.title.text=Export selected rows to CSV"})
89  private ExportCSVAction() {
90  super(Bundle.ExportCSV_title_text());
91  }
92 
100  @Override
101  public void actionPerformed(ActionEvent e) {
102  Collection<? extends Node> selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class);
103  saveNodesToCSV(selectedNodes, (Component)e.getSource());
104  }
105 
112  @NbBundle.Messages({
113  "# {0} - Output file",
114  "ExportCSV.saveNodesToCSV.fileExists=File {0} already exists",
115  "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available",
116  "ExportCSV.saveNodesToCSV.empty=No data to export"})
117  public static void saveNodesToCSV(Collection<? extends Node> nodesToExport, Component component) {
118 
119  if (nodesToExport.isEmpty()) {
120  MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_empty());
121  return;
122  }
123 
124  try {
125  // Set up the file chooser with a default name and either the Export
126  // folder or the last used folder.
127  String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode());
128  JFileChooser fileChooser = new JFileChooser();
129  fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows())));
130  fileChooser.setSelectedFile(new File(fileName));
131  fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv"));
132 
133  int returnVal = fileChooser.showSaveDialog(component);
134  if (returnVal == JFileChooser.APPROVE_OPTION) {
135 
136  // Get the file name, appending .csv if necessary
137  File selectedFile = fileChooser.getSelectedFile();
138  if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
139  selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
140  }
141 
142  // Save the directory used for next time
143  updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows());
144 
145  if (selectedFile.exists()) {
146  logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS
147  MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_fileExists(selectedFile));
148  return;
149  }
150 
151  CSVWriter writer = new CSVWriter(nodesToExport, selectedFile);
152  writer.execute();
153  }
154  } catch (NoCurrentCaseException ex) {
155  JOptionPane.showMessageDialog(component, Bundle.ExportCSV_saveNodesToCSV_noCurrentCase());
156  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
157  }
158  }
159 
167  private static String getDefaultOutputFileName(Node parent) {
168  String dateStr = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS", Calendar.getInstance());
169 
170  if (parent != null) {
171  // The first value in the property set is generally a reasonable name
172  for (PropertySet set : parent.getPropertySets()) {
173  for (Property<?> prop : set.getProperties()) {
174  try {
175  String parentName = prop.getValue().toString();
176 
177  // Strip off the count (if present)
178  parentName = parentName.replaceAll("\\([0-9]+\\)$", "");
179 
180  // Strip out any invalid characters
181  parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_");
182 
183  return parentName + " " + dateStr;
184  } catch (IllegalAccessException | InvocationTargetException ex) {
185  logger.log(Level.WARNING, "Failed to get property set value as string", ex);
186  }
187  }
188  }
189  }
190  return DEFAULT_FILENAME + " " + dateStr;
191  }
192 
200  private static String getExportDirectory(Case openCase) {
201  String caseExportPath = openCase.getExportDirectory();
202 
203  if (userDefinedExportPath == null) {
204  return caseExportPath;
205  }
206 
207  File file = new File(userDefinedExportPath);
208  if (file.exists() == false || file.isDirectory() == false) {
209  return caseExportPath;
210  }
211 
212  return userDefinedExportPath;
213  }
214 
224  private static void updateExportDirectory(String exportPath, Case openCase) {
225  if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
226  userDefinedExportPath = null;
227  } else {
228  userDefinedExportPath = exportPath;
229  }
230  }
231 
232 
236  private static class CSVWriter extends SwingWorker<Object, Void> {
237 
238  private static final Logger logger = Logger.getLogger(CSVWriter.class.getName());
239  private ProgressHandle progress;
240 
241  private final Collection<? extends Node> nodesToExport;
242  private final File outputFile;
243 
249  CSVWriter(Collection<? extends Node> nodesToExport, File outputFile) {
250  this.nodesToExport = nodesToExport;
251  this.outputFile = outputFile;
252  }
253 
254  @NbBundle.Messages({"CSVWriter.progress.extracting=Exporting to CSV file",
255  "CSVWriter.progress.cancelling=Cancelling"})
256  @Override
257  protected Object doInBackground() throws Exception {
258  if (nodesToExport.isEmpty()) {
259  return null;
260  }
261 
262  // Set up progress bar.
263  final String displayName = Bundle.CSVWriter_progress_extracting();
264  progress = ProgressHandle.createHandle(displayName, new Cancellable() {
265  @Override
266  public boolean cancel() {
267  if (progress != null) {
268  progress.setDisplayName(Bundle.CSVWriter_progress_cancelling());
269  }
270  return ExportCSVAction.CSVWriter.this.cancel(true);
271  }
272  });
273  progress.start();
274  progress.switchToIndeterminate();
275 
276  try (BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) {
277  // Write BOM
278  br.write('\ufeff');
279 
280  // Write the header
281  List<String> headers = new ArrayList<>();
282  PropertySet[] sets = nodesToExport.iterator().next().getPropertySets();
283  for(PropertySet set : sets) {
284  for (Property<?> prop : set.getProperties()) {
285  if ( ! columnsToSkip.contains(prop.getDisplayName())) {
286  headers.add(prop.getDisplayName());
287  }
288  }
289  }
290  br.write(listToCSV(headers));
291 
292  // Write each line
293  Iterator<?> nodeIterator = nodesToExport.iterator();
294  while (nodeIterator.hasNext()) {
295  if (this.isCancelled()) {
296  break;
297  }
298 
299  Node node = (Node)nodeIterator.next();
300  List<String> values = new ArrayList<>();
301  sets = node.getPropertySets();
302  for(PropertySet set : sets) {
303  for (Property<?> prop : set.getProperties()) {
304  if ( ! columnsToSkip.contains(prop.getDisplayName())) {
305  values.add(escapeQuotes(prop.getValue().toString()));
306  }
307  }
308  }
309  br.write(listToCSV(values));
310  }
311  }
312 
313  return null;
314  }
315 
323  private String escapeQuotes(String original) {
324  return original.replaceAll("\"", "\\\\\"");
325  }
326 
334  private String listToCSV(List<String> values) {
335  return "\"" + String.join("\",\"", values) + "\"\n";
336  }
337 
338  @NbBundle.Messages({"CSVWriter.done.notifyMsg.error=Error exporting to CSV file",
339  "# {0} - Output file",
340  "CSVWriter.done.notifyMsg.success=Wrote to {0}"})
341  @Override
342  protected void done() {
343  boolean msgDisplayed = false;
344  try {
345  super.get();
346  } catch (InterruptedException | ExecutionException ex) {
347  logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
348  MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_error());
349  msgDisplayed = true;
350  } catch (java.util.concurrent.CancellationException ex) {
351  // catch and ignore if we were cancelled
352  } finally {
353  progress.finish();
354  if (!this.isCancelled() && !msgDisplayed) {
355  MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_success(outputFile));
356  }
357  }
358  }
359  }
360 }
static synchronized ExportCSVAction getInstance()
static void updateExportDirectory(String exportPath, Case openCase)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void saveNodesToCSV(Collection<?extends Node > nodesToExport, Component component)

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.