diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java index 6def78b83d..0766316e67 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java @@ -392,85 +392,69 @@ public final class ContentUtils { public static void extract(Content cntnt, java.io.File dest, ProgressHandle progress, SwingWorker worker) { cntnt.accept(new ExtractFscContentVisitor<>(dest, progress, worker, true)); } + + /** + * Base method writing a file to disk. + * + * @param file The TSK content file. + * @param dest The disk location where the content will be written. + * @param progress progress bar handle to update, if available. null + * otherwise + * @param worker the swing worker background thread the process runs + * within, or null, if in the main thread, used to + * handle task cancellation + * @param source true if source file + * + * @throws IOException + */ + protected void writeFile(Content file, java.io.File dest, ProgressHandle progress, SwingWorker worker, boolean source) throws IOException { + ContentUtils.writeToFile(file, dest, progress, worker, source); + } - @Override - public Void visit(File file) { + /** + * Visits a TSK content file and writes that file to disk. + * @param file The file to be written. + * @param fileType The file type (i.e. "derived file") for error logging. + * @return null. + */ + protected Void visitFile(Content file, String fileType) { try { - ContentUtils.writeToFile(file, dest, progress, worker, source); + writeFile(file, dest, progress, worker, source); } catch (ReadContentInputStreamException ex) { logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS } catch (IOException ex) { logger.log(Level.SEVERE, - String.format("Error extracting file '%s' (id=%d) to '%s'.", - file.getName(), file.getId(), dest.getAbsolutePath()), ex); //NON-NLS + String.format("Error extracting %s '%s' (id=%d) to '%s'.", + fileType, file.getName(), file.getId(), dest.getAbsolutePath()), ex); //NON-NLS } return null; } + + @Override + public Void visit(File file) { + return visitFile(file, "file"); + } @Override public Void visit(LayoutFile file) { - try { - ContentUtils.writeToFile(file, dest, progress, worker, source); - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, - String.format("Error reading file '%s' (id=%d).", - file.getName(), file.getId()), ex); //NON-NLS - } catch (IOException ex) { - logger.log(Level.SEVERE, - String.format("Error extracting unallocated content file '%s' (id=%d) to '%s'.", - file.getName(), file.getId(), dest.getAbsolutePath()), ex); //NON-NLS - } - return null; + return visitFile(file, "unallocated content file"); } @Override public Void visit(DerivedFile file) { - try { - ContentUtils.writeToFile(file, dest, progress, worker, source); - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, - String.format("Error reading file '%s' (id=%d).", - file.getName(), file.getId()), ex); //NON-NLS - } catch (IOException ex) { - logger.log(Level.SEVERE, - String.format("Error extracting derived file '%s' (id=%d) to '%s'.", - file.getName(), file.getId(), dest.getAbsolutePath()), ex); //NON-NLS - } - return null; + return visitFile(file, "derived file"); } @Override public Void visit(LocalFile file) { - try { - ContentUtils.writeToFile(file, dest, progress, worker, source); - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, - String.format("Error reading file '%s' (id=%d).", - file.getName(), file.getId()), ex); //NON-NLS - } catch (IOException ex) { - logger.log(Level.SEVERE, - String.format("Error extracting local file '%s' (id=%d) to '%s'.", - file.getName(), file.getId(), dest.getAbsolutePath()), ex); //NON-NLS - } - return null; + return visitFile(file, "local file"); } @Override public Void visit(SlackFile file) { - try { - ContentUtils.writeToFile(file, dest, progress, worker, source); - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, - String.format("Error reading file '%s' (id=%d).", - file.getName(), file.getId()), ex); //NON-NLS - } catch (IOException ex) { - logger.log(Level.SEVERE, - String.format("Error extracting slack file '%s' (id=%d) to '%s'.", - file.getName(), file.getId(), dest.getAbsolutePath()), ex); //NON-NLS - } - return null; + return visitFile(file, "slack file"); } @Override @@ -493,6 +477,20 @@ public final class ContentUtils { + content.getName(); return new java.io.File(path); } + + /** + * Returns a visitor to visit any child content. + * @param childFile The disk location where the content will be written. + * @param progress progress bar handle to update, if available. null + * otherwise + * @param worker the swing worker background thread the process runs + * within, or null, if in the main thread, used to + * handle task cancellation + * @return + */ + protected ExtractFscContentVisitor getChildVisitor(java.io.File childFile, ProgressHandle progress, SwingWorker worker) { + return new ExtractFscContentVisitor<>(childFile, progress, worker, false); + } public Void visitDir(AbstractFile dir) { @@ -509,8 +507,7 @@ public final class ContentUtils { for (Content child : dir.getChildren()) { if (child instanceof AbstractFile) { //ensure the directory's artifact children are ignored java.io.File childFile = getFsContentDest(child); - ExtractFscContentVisitor childVisitor - = new ExtractFscContentVisitor<>(childFile, progress, worker, false); + ExtractFscContentVisitor childVisitor = getChildVisitor(childFile, progress, worker); // If this is the source directory of an extract it // will have a progress and worker, and will keep track // of the progress bar's progress diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 0efe3bdcfb..6a585d724f 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -43,6 +43,8 @@ public final class ExtractAction extends AbstractAction { } return instance; } + + private final ExtractActionHelper extractor = new ExtractActionHelper(); /** * Private constructor for the action. @@ -61,7 +63,6 @@ public final class ExtractAction extends AbstractAction { public void actionPerformed(ActionEvent e) { Lookup lookup = Utilities.actionsGlobalContext(); Collection selectedFiles =lookup.lookupAll(AbstractFile.class); - ExtractActionHelper extractor = new ExtractActionHelper(); extractor.extract(e, selectedFiles); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java index 5a28bd9c12..386240ea86 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.directorytree.actionhelpers; import java.awt.Component; import java.awt.event.ActionEvent; import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -42,8 +44,10 @@ 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.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; /** * Helper class for methods needed by actions which extract files. @@ -52,7 +56,7 @@ public class ExtractActionHelper { private final Logger logger = Logger.getLogger(ExtractActionHelper.class.getName()); private String userDefinedExportPath; - + private final JFileChooserFactory extractFileHelper = new JFileChooserFactory(); private final JFileChooserFactory extractFilesHelper = new JFileChooserFactory(); @@ -68,12 +72,7 @@ public class ExtractActionHelper { if (selectedFiles.size() > 1) { extractFiles(event, selectedFiles); } else if (selectedFiles.size() == 1) { - AbstractFile source = selectedFiles.iterator().next(); - if (source.isDir()) { - extractFiles(event, selectedFiles); - } else { - extractFile(event, selectedFiles.iterator().next()); - } + extractFile(event, selectedFiles.iterator().next()); } } @@ -83,7 +82,11 @@ public class ExtractActionHelper { * @param event * @param selectedFile Selected file */ - @NbBundle.Messages({"ExtractActionHelper.noOpenCase.errMsg=No open case available."}) + @NbBundle.Messages({"ExtractActionHelper.noOpenCase.errMsg=No open case available.", + "ExtractActionHelper.extractOverwrite.title=Export to csv file", + "# {0} - fileName", + "ExtractActionHelper.extractOverwrite.msg=A file already exists at {0}. Do you want to overwrite the existing file?" + }) private void extractFile(ActionEvent event, AbstractFile selectedFile) { Case openCase; try { @@ -269,6 +272,68 @@ public class ExtractActionHelper { } } + /** + * A file content extraction visitor that handles for the UI designed to + * handle file name conflicts by appending the object id to the file name. + */ + private static class UIExtractionVisitor extends ExtractFscContentVisitor { + + /** + * @param file The TSK content file. + * @param dest The disk location where the content will be written. + * @param progress progress bar handle to update, if available. null + * otherwise + * @param worker the swing worker background thread the process runs + * within, or null, if in the main thread, used to + * handle task cancellation + * @param source true if source file + */ + UIExtractionVisitor(File dest, ProgressHandle progress, SwingWorker worker, boolean source) { + super(dest, progress, worker, source); + } + + /** + * Writes content and children to disk. + * + * @param content The root content. + * @param file The TSK content file. + * @param dest The disk location where the content will be written. + * @param progress progress bar handle to update, if available. null + * otherwise + * @param worker the swing worker background thread the process runs + * within, or null, if in the main thread, used to + * handle task cancellation + * @param source true if source file + */ + static void writeContent(Content content, File dest, ProgressHandle progress, SwingWorker worker) { + content.accept(new UIExtractionVisitor<>(dest, progress, worker, true)); + } + + + @Override + protected void writeFile(Content file, File dest, ProgressHandle progress, SwingWorker worker, boolean source) throws IOException { + File destFile; + if (dest.exists()) { + String parent = dest.getParent(); + String fileName = dest.getName(); + String objIdFileName = MessageFormat.format("{0}-{1}", file.getId(), fileName); + destFile = new File(parent, objIdFileName); + } else { + destFile = dest; + } + + super.writeFile(file, destFile, progress, worker, source); + } + + @Override + protected ExtractFscContentVisitor getChildVisitor(File childFile, ProgressHandle progress, SwingWorker worker) { + return new UIExtractionVisitor(childFile, progress, worker, false); + } + + + + } + /** * Thread that does the actual extraction work */ @@ -321,8 +386,7 @@ public class ExtractActionHelper { // Do the extraction tasks. for (FileExtractionTask task : this.extractionTasks) { progress.progress(Bundle.ExtractActionHelper_progress_fileExtracting(task.destination.getName())); - - ContentUtils.ExtractFscContentVisitor.extract(task.source, task.destination, null, this); + UIExtractionVisitor.writeContent(task.source, task.destination, null, this); } return null;