From e1d41a5b3011416e938f793eace71109a1c7a5f4 Mon Sep 17 00:00:00 2001 From: 0xNF Date: Thu, 29 Nov 2012 13:12:16 -0500 Subject: [PATCH 01/13] Changes to the AddImage Panels -Displays an empty string instead of "Folder Information Unavailable" when processing something with no name -CurrentDirectoryFetcher is now static --- .../casemodule/AddImageVisualPanel2.java | 3 ++- .../casemodule/AddImageWizardPanel3.java | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java index 9f269f60a6..f42b418824 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java @@ -53,6 +53,7 @@ final class AddImageVisualPanel2 extends JPanel { infoPanel.add(progressLabel); infoPanel.add(Box.createRigidArea(new Dimension(10, 10))); //spacer this.jScrollPane1.setBorder(null); + this.TextArea_CurrentDirectory.setBackground(this.getBackground()); } void resetInfoPanel() { @@ -108,7 +109,7 @@ final class AddImageVisualPanel2 extends JPanel { * @param dir the text to update with */ public void changeCurrentDir(String dir){ - this.TextArea_CurrentDirectory.setText(dir.trim().isEmpty() ? "Folder Information Unavailable" : dir); + this.TextArea_CurrentDirectory.setText(dir); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java index 237ed01945..6ffa8fd944 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java @@ -205,11 +205,16 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * */ - private class CurrentDirectoryFetcher extends SwingWorker { + private static class CurrentDirectoryFetcher extends SwingWorker { AddImgTask task; + JProgressBar prog; + AddImageVisualPanel2 wiz; + AddImageProcess proc; - CurrentDirectoryFetcher(AddImgTask task){ - this.task = task; + CurrentDirectoryFetcher(JProgressBar prog, AddImageVisualPanel2 wiz, AddImageProcess proc){ + this.wiz = wiz; + this.proc = proc; + this.prog = prog; } /** @@ -218,13 +223,13 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { @Override protected Integer doInBackground(){ try{ - while(task.progressBar.getValue() < 100 || task.progressBar.isIndeterminate()){ + while(prog.getValue() < 100 || prog.isIndeterminate()){ //TODO Rely on state variable in AddImgTask class EventQueue.invokeLater(new Runnable() { @Override public void run() { - wizPanel.getComponent().changeCurrentDir(process.currentDirectory()); + wiz.changeCurrentDir(proc.currentDirectory()); } @@ -304,7 +309,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { process = currentCase.makeAddImageProcess(timeZone, true, noFatOrphans); - fetcher = new CurrentDirectoryFetcher(this); + fetcher = new CurrentDirectoryFetcher(this.progressBar, wizPanel.getComponent(), process); cancelledWhileRunning.enable(); try { wizPanel.setStateStarted(); From 6b84d33c40bc089c523ec9e7113906ef2854b92b Mon Sep 17 00:00:00 2001 From: 0xNF Date: Thu, 29 Nov 2012 14:49:09 -0500 Subject: [PATCH 02/13] Added ability to extract unallocated space to a single file per volume --- .../directorytree/DataResultFilterNode.java | 2 +- .../directorytree/ExtractUnallocAction.java | 269 ++++++++++++++++++ 2 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 36b10f81a7..2d9de3dd45 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -160,7 +160,7 @@ public class DataResultFilterNode extends FilterNode { List actions = new ArrayList(); actions.add(new NewWindowViewAction("View in New Window", vol)); actions.addAll(ShowDetailActionVisitor.getActions(vol.getLookup().lookup(Content.class))); - + actions.add(new ExtractUnallocAction("Extract Unallocated Space as Single File", vol)); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java new file mode 100644 index 0000000000..0fc49e8078 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -0,0 +1,269 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 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.directorytree; + +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.VolumeNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.LayoutDirectory; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extracts all the unallocated space as a single file + */ +public final class ExtractUnallocAction extends AbstractAction{ + + private static List llf; + private static Content vol; + private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); + + ExtractUnallocAction(String title, VolumeNode volume) { + super(title); + vol = volume.getLookup().lookup(Content.class); + llf = getUnallocFiles(vol); + Collections.sort(llf, new SortObjId()); + } + + /** + * Writes the unallocated files to $CaseDir/Export/ImgName-Unalloc-ImgObjectID-VolumeID.dat + * @param e + */ + @Override + public void actionPerformed(ActionEvent e) { + if (llf != null && llf.size() > 0) { + try { + String imgName = vol.getImage().getName(); //Image Name + long imgObjID = vol.getImage().getId(); //Image ID + long volumeID = vol.getId(); //Volume ID + String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat"; + //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat + File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName); + if (unalloc.exists()) { + int res = JOptionPane.showConfirmDialog(new Frame(), "The Unalloc File for this volume, " + UnallocName + " already exists, do you want to replace it?"); + if (res == JOptionPane.YES_OPTION) { + unalloc.delete(); + } else { + return; + } + } + ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc, this); + uw.execute(); + }catch (TskCoreException tce) { + logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); + } + } else { + logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + vol.getId() + ", but its list of unallocated files was empty or null"); + } + } + + private List getUnallocFiles(Content c) { + UnallocVisitor uv = new UnallocVisitor(); + logger.log(Level.INFO, "Sending out Unallocated File Visitor"); + try { + return c.getChildren().get(0).accept(uv); //Launching it on the root directory + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); + } + return null; + } + + + /** + * Private class for dispatching the file IO in a background thread. + */ + private class ExtractUnallocWorker extends SwingWorker { + + ExtractUnallocAction ua; + File path; + private ProgressHandle progress; + private boolean canceled = false; + + ExtractUnallocWorker(File path, ExtractUnallocAction ua) { + this.path = path; + this.ua = ua; + } + + @Override + protected Integer doInBackground() { + try { + progress = ProgressHandleFactory.createHandle("Extracting " + path.getName(), new Cancellable() { + @Override + public boolean cancel() { + logger.log(Level.INFO, "Canceling extraction of Unalloc file " + path.getName()); + canceled = true; + if (progress != null) { + progress.setDisplayName(path.getName() + " (Cancelling...)"); + } + return true; + } + }); + FileOutputStream fos = new FileOutputStream(path); + int MAX_BYTES = 8192; + byte[] buf = new byte[MAX_BYTES]; //read 8k at a time + logger.log(Level.INFO, "Writing Unalloc file to " + path.getPath()); + + progress.start(llf.size()); + int count = 0; + for (LayoutFile f : llf) { + long offset = 0L; + while (offset != f.getSize() && !canceled) { + offset += f.read(buf, offset, MAX_BYTES); //Offset + Bytes read + fos.write(buf); + } + progress.progress(count++); + } + progress.finish(); + fos.flush(); + fos.close(); + + if(canceled){ + path.delete(); + logger.log(Level.INFO, "Canceled extraction of " + path.getName() + " and deleted file"); + } + else{ + logger.log(Level.INFO, "Finished writing unalloc file"); + } + } catch (IOException ioe) { + logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ioe); + return -1; + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); + return -1; + } + return 1; + } + } + + private static class UnallocVisitor extends ContentVisitor.Default> { + + /** + * If the volume has no FileSystem, then it will call this method to return the single instance of unallocated space. + * @param lf the LayoutFile the visitor encountered + * @return A list of size 1 + */ + @Override + public List visit(final org.sleuthkit.datamodel.LayoutFile lf) { + return new ArrayList() { + { + add(lf); + } + }; + } + + /** + * If the visitor finds a FileSystem, it will filter the results for directories and return on the Root Dir. + * @param fs the FileSystem the visitor encountered + * @return A list containing the layout files from subsequent Visits() + */ + @Override + public List visit(FileSystem fs) { + try { + for (Content c : fs.getChildren()){ + if(((AbstractFile)c).isRoot()){ + return c.accept(this); + } + } + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), tce); + } + return null; + } + + /** + * LayoutDirectory has all the Layout(Unallocated) files + * @param ld LayoutDirectory the visitor encountered + * @return A list containing all the LayoutFile in ld + */ + @Override + public List visit(LayoutDirectory ld){ + try{ + List lflst = new ArrayList(); + for(Content layout : ld.getChildren()){ + lflst.add((LayoutFile)layout); + } + return lflst; + } catch(TskCoreException tce){ + logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory in " + vol.getId(), tce); + } + return null; + } + + /** + * The only time this visitor should ever encounter a directory is when parsing over Root + * @param dir the directory this visitor encountered + * @return A list containing LayoutFiles encountered during subsequent Visits() + */ + @Override + public List visit(Directory dir) { + try { + for (Content c : dir.getChildren()) { + if(c instanceof LayoutDirectory){ + return c.accept(this); + } + } + }catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), tce); + } + return null; + } + + @Override + protected List defaultVisit(Content cntnt) { + return null; + } + } + + private class SortObjId implements Comparator{ + + @Override + public int compare(LayoutFile o1, LayoutFile o2) { + if(o1.getId() == o2.getId()){ + return 0; + } + if(o1.getId() > o2.getId()){ + return -1; + } + else{ + return 1; + } + } + } +} From ae7a971be491206d2815c1c553c8a3c65661d97b Mon Sep 17 00:00:00 2001 From: 0xNF Date: Thu, 29 Nov 2012 16:33:08 -0500 Subject: [PATCH 03/13] ExtractUnalloc now prevents users from running extract on a single volume more than once when it's already running. --- .../directorytree/ExtractUnallocAction.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index 0fc49e8078..c7e224a052 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -53,6 +53,7 @@ public final class ExtractUnallocAction extends AbstractAction{ private static List llf; private static Content vol; + volatile static boolean running = false; private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); ExtractUnallocAction(String title, VolumeNode volume) { @@ -76,6 +77,10 @@ public final class ExtractUnallocAction extends AbstractAction{ String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat"; //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName); + if(running){ + JOptionPane.showMessageDialog(new Frame(), "Extract is already running on this volume. Please select a different volume."); + return; + } if (unalloc.exists()) { int res = JOptionPane.showConfirmDialog(new Frame(), "The Unalloc File for this volume, " + UnallocName + " already exists, do you want to replace it?"); if (res == JOptionPane.YES_OPTION) { @@ -84,7 +89,7 @@ public final class ExtractUnallocAction extends AbstractAction{ return; } } - ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc, this); + ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc); uw.execute(); }catch (TskCoreException tce) { logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); @@ -111,14 +116,13 @@ public final class ExtractUnallocAction extends AbstractAction{ */ private class ExtractUnallocWorker extends SwingWorker { - ExtractUnallocAction ua; File path; private ProgressHandle progress; private boolean canceled = false; - ExtractUnallocWorker(File path, ExtractUnallocAction ua) { + ExtractUnallocWorker(File path) { this.path = path; - this.ua = ua; + running = true; } @Override @@ -167,6 +171,8 @@ public final class ExtractUnallocAction extends AbstractAction{ } catch (TskCoreException tce) { logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); return -1; + }finally{ + running = false; } return 1; } @@ -257,7 +263,7 @@ public final class ExtractUnallocAction extends AbstractAction{ public int compare(LayoutFile o1, LayoutFile o2) { if(o1.getId() == o2.getId()){ return 0; - } + } if(o1.getId() > o2.getId()){ return -1; } From 8d81bd05705269efa9a2de32f71274ea6bfa9e0a Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 29 Nov 2012 18:33:26 -0500 Subject: [PATCH 04/13] AUT-683 Fix text direction setting in Text view --- .../keywordsearch/ExtractedContentPanel.java | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 567d6fcaaa..b9894f83f5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -25,6 +25,7 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.SwingWorker; @@ -39,6 +40,7 @@ import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit.HTMLFactory; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.TextUtil; /** @@ -320,7 +322,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { */ void setSources(List sources) { sourceComboBox.removeAllItems(); - setPanelText(null); + setPanelText(null, false); for (MarkupSource ms : sources) { sourceComboBox.addItem(ms); @@ -351,16 +353,29 @@ class ExtractedContentPanel extends javax.swing.JPanel { return (MarkupSource) sourceComboBox.getSelectedItem(); } - private void setPanelText(String text) { - if (text == null ) { + private void setPanelText(String text, boolean detectDirection) { + if (text == null) { text = ""; } - - //detect text direction using first 1024 chars and set it - final int maxOrientChars = Math.min(text.length(), 1024); - final String orientDetectText = text.substring(0, maxOrientChars); - extractedTextPane.applyComponentOrientation(TextUtil.getTextDirection(orientDetectText)); - + + if (detectDirection) { + //detect text direction using first 1024 chars and set it + //get first up to 1024 chars, strip
 tag and unescape html to get the string on which to detect
+            final int len = text.length();
+            final int prefixLen = "
".length();
+            if (len > prefixLen) {
+                final int maxOrientChars = Math.min(len, 1024);
+                final String orientDetectText = EscapeUtil.unEscapeHtml(text.substring(prefixLen, maxOrientChars));
+                ComponentOrientation direction = TextUtil.getTextDirection(orientDetectText);
+                //logger.log(Level.INFO, "ORIENTATION LEFT TO RIGHT: " + direction.isLeftToRight());
+                extractedTextPane.applyComponentOrientation(direction);
+            } else {
+                extractedTextPane.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+            }
+        } else {
+            extractedTextPane.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+        }
+
         extractedTextPane.setText(text);
         extractedTextPane.setCaretPosition(0);
     }
@@ -572,16 +587,15 @@ class ExtractedContentPanel extends javax.swing.JPanel {
         }
     }
 
-    
     /**
-     * Gets and sets new markup.  Updates GUI in GUI thread and gets markup in background thread.
-     * To be invoked from GUI thread only.
+     * Gets and sets new markup. Updates GUI in GUI thread and gets markup in
+     * background thread. To be invoked from GUI thread only.
      */
     private void setMarkup(MarkupSource source) {
-        setPanelText("Loading text... Please wait");
+        setPanelText("Loading text... Please wait", false);
         new SetMarkup(source).execute();
     }
-    
+
     /**
      * Swingworker to get makrup source content String from Solr in background
      * thread and then set the panel text in the EDT Helps not to block the UI
@@ -603,7 +617,7 @@ class ExtractedContentPanel extends javax.swing.JPanel {
             progress.setDisplayName("Loading text");
             progress.start();
             progress.switchToIndeterminate();
-            
+
             markup = source.getMarkup();
             return null;
         }
@@ -613,9 +627,9 @@ class ExtractedContentPanel extends javax.swing.JPanel {
             //super.done();
             progress.finish();
             if (markup != null) {
-                setPanelText(markup);
+                setPanelText(markup, true);
             } else {
-                setPanelText("");
+                setPanelText("", false);
             }
             updateControls(source);
 

From a48f5e674877cc53fe1245c91dd142cc5073565f Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Thu, 29 Nov 2012 18:40:35 -0500
Subject: [PATCH 05/13] search for files with same md5sum if at least 1 hash
 exists in db

---
 .../hashdatabase/HashDbSearchAction.java      | 33 ++++++++-----------
 1 file changed, 13 insertions(+), 20 deletions(-)

diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java
index a739a1259a..f80e9063be 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java
@@ -32,21 +32,21 @@ import org.sleuthkit.datamodel.FsContent;
 /**
  * Searches for FsContent Files with the same MD5 hash as the given Node's
  * FsContent's MD5 hash. This action should only be available from Nodes with
- * specific Content attached; it is manually programmed into a Node's available actions.
+ * specific Content attached; it is manually programmed into a Node's available
+ * actions.
  */
 public class HashDbSearchAction extends CallableSystemAction implements HashSearchProvider {
 
     private static final InitializeContentVisitor initializeCV = new InitializeContentVisitor();
     private FsContent fsContent;
-
     private static HashDbSearchAction instance = null;
 
     HashDbSearchAction() {
         super();
     }
-    
+
     public static HashDbSearchAction getDefault() {
-        if(instance == null){
+        if (instance == null) {
             instance = new HashDbSearchAction();
         }
         return instance;
@@ -60,7 +60,7 @@ public class HashDbSearchAction extends CallableSystemAction implements HashSear
     }
 
     /**
-     * Returns the FsContent if it is supported, otherwise null. It should 
+     * Returns the FsContent if it is supported, otherwise null. It should
      * realistically never return null or a Directory, only a File.
      */
     private static class InitializeContentVisitor extends ContentVisitor.Default {
@@ -82,28 +82,21 @@ public class HashDbSearchAction extends CallableSystemAction implements HashSear
     }
 
     /**
-     * Find all files with the same MD5 hash as this' fsContent. fsContent should
-     * be previously set by calling the search function, which in turn calls performAction.
+     * Find all files with the same MD5 hash as this' fsContent. fsContent
+     * should be previously set by calling the search function, which in turn
+     * calls performAction.
      */
     @Override
     public void performAction() {
-        // Make sure all files have an md5 hash
-        if(HashDbSearcher.allFilesMd5Hashed()) {
+        // Make sure at least 1 file has an md5 hash
+        if (HashDbSearcher.countFilesMd5Hashed() > 0) {
             doSearch();
-        // and if not, warn the user
-        } else if(HashDbSearcher.countFilesMd5Hashed() > 0) {
-            Object selected = JOptionPane.showConfirmDialog(null, "Not all files have MD5 hashes. "
-                    + "Search results will be incomplete.\n"
-                    + "Would you like to search anyway?", "File Search by MD5 Hash", JOptionPane.YES_NO_OPTION);
-            if(selected.equals(JOptionPane.YES_OPTION)) {
-                doSearch();
-            }
         } else {
-                JOptionPane.showMessageDialog(null, "No files currently have an MD5 hash.",
-                        "File Search by MD5 Hash", JOptionPane.ERROR_MESSAGE);
+            JOptionPane.showMessageDialog(null, "No files currently have an MD5 hash calculated, run HashDB ingest first.",
+                    "File Search by MD5 Hash", JOptionPane.ERROR_MESSAGE);
         }
     }
-    
+
     private void doSearch() {
         HashDbSearchThread hashThread = new HashDbSearchThread(fsContent);
         hashThread.execute();

From ed8d4cab8685e20edd889c844aa0f7ef77639b01 Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Thu, 29 Nov 2012 18:45:52 -0500
Subject: [PATCH 06/13] allow to run hash search from dialog if at least 1 hash
 present

---
 .../autopsy/hashdatabase/HashDbSearchPanel.java | 17 +++--------------
 1 file changed, 3 insertions(+), 14 deletions(-)

diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java
index cc79736d54..7c50fc25f6 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java
@@ -292,20 +292,9 @@ public class HashDbSearchPanel extends javax.swing.JPanel implements ActionListe
     boolean search() {
         // Check if any hashed have been entered
         if(hashTable.getRowCount()!=0) {
-            // Make sure all files have an md5 hash
-            if(HashDbSearcher.allFilesMd5Hashed()) {
-                return doSearch();
-            // and if not, warn the user
-            } else if(HashDbSearcher.countFilesMd5Hashed() > 0) {
-                errorField.setVisible(false);
-                Object selected = JOptionPane.showConfirmDialog(null, "Not all files have MD5 hashes. "
-                        + "Search results will be incomplete.\n"
-                        + "Would you like to search anyway?", "File Search by MD5 Hash", JOptionPane.YES_NO_OPTION);
-                if(selected.equals(JOptionPane.YES_OPTION)) {
-                    return doSearch();
-                } else {
-                    return false;
-                }
+            // Make sure at least 1 file has an md5 hash
+           if(HashDbSearcher.countFilesMd5Hashed() > 0) {
+               return doSearch();
             } else {
                 JOptionPane.showMessageDialog(null, "No files currently have an MD5 hash.",
                         "File Search by MD5 Hash", JOptionPane.ERROR_MESSAGE);

From b38651e2593cf492023181f9ef4235350df914aa Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 10:34:12 -0500
Subject: [PATCH 07/13] CaseSchema should no longer report errors when the case
 number contains strings

---
 Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd b/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
index 41e3724f69..30a86d859a 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
@@ -6,9 +6,9 @@
 
 
 
-
+
 
-
+
 
 
 

From c93928cf4d639064673b8e9894dabb7584ce0c27 Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 10:43:33 -0500
Subject: [PATCH 08/13] ExtractUnallocAction now applies to all types of
 VolumeNodes, not just ones in the middle panel

---
 .../autopsy/datamodel/VolumeNode.java         |  2 +
 .../directorytree/DataResultFilterNode.java   |  1 -
 .../directorytree/ExtractUnallocAction.java   | 44 ++++++++++---------
 .../ShowDetailActionVisitor.java              | 42 +++++++++++-------
 4 files changed, 52 insertions(+), 37 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java
index 10aaeccf1d..091ac4797e 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java
@@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel;
 
 import javax.swing.Action;
 import org.openide.nodes.Sheet;
+import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction;
 import org.sleuthkit.datamodel.Volume;
 
 /**
@@ -66,6 +67,7 @@ public class VolumeNode extends AbstractContentNode {
     public Action[] getActions(boolean popup) {
         return new Action[]{ //new ShowDetailAction("Volume Details", this.getName(), this),
                 //new ShowDetailAction("File System Details", this.getName(), this)
+            //new ExtractUnallocAction("Extract Unallocated Files to single Single", this)
                 };
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index 2d9de3dd45..e0ad518535 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -160,7 +160,6 @@ public class DataResultFilterNode extends FilterNode {
             List actions = new ArrayList();
             actions.add(new NewWindowViewAction("View in New Window", vol));
             actions.addAll(ShowDetailActionVisitor.getActions(vol.getLookup().lookup(Content.class)));
-            actions.add(new ExtractUnallocAction("Extract Unallocated Space as Single File", vol));
             return actions;
         }
 
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java
index c7e224a052..f8018115ed 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java
@@ -45,21 +45,32 @@ import org.sleuthkit.datamodel.FileSystem;
 import org.sleuthkit.datamodel.LayoutDirectory;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.Volume;
 
 /**
  * Extracts all the unallocated space as a single file
  */
 public final class ExtractUnallocAction extends AbstractAction{
 
-    private static List llf;
-    private static Content vol;
+    private List llf;
+    long VolumeId;
+    String ImageName;
+    long ImageId;
     volatile static boolean running = false;
     private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
     
-    ExtractUnallocAction(String title, VolumeNode volume) {
+    public ExtractUnallocAction(String title, Volume volu){
         super(title);
-        vol = volume.getLookup().lookup(Content.class);
-        llf = getUnallocFiles(vol);
+        VolumeId = volu.getId();
+        try{
+            ImageName = volu.getImage().getName();
+            ImageId = volu.getImage().getId();
+        } catch(TskCoreException tce){
+            logger.log(Level.WARNING, "Unable to properly create ExtractUnallocAction, extraction may be incomplete", tce);
+            ImageName = "";
+            ImageId = 0;
+        }
+        llf = getUnallocFiles(volu);
         Collections.sort(llf, new SortObjId());
     }
 
@@ -70,15 +81,11 @@ public final class ExtractUnallocAction extends AbstractAction{
     @Override
     public void actionPerformed(ActionEvent e) {
         if (llf != null && llf.size() > 0) {
-            try {
-                String imgName = vol.getImage().getName();   //Image Name
-                long imgObjID = vol.getImage().getId();      //Image ID
-                long volumeID = vol.getId();                 //Volume ID
-                String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat";
+                String UnallocName = ImageName + "-Unalloc-" + ImageId + "-" + VolumeId + ".dat";
                 //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat
                 File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName);
                 if(running){
-                    JOptionPane.showMessageDialog(new Frame(), "Extract is already running on this volume. Please select a different volume.");
+                    JOptionPane.showMessageDialog(new Frame(), "Unallocated Space is already running on this volume. Please select a different volume.");
                     return;
                 }
                 if (unalloc.exists()) {
@@ -91,11 +98,8 @@ public final class ExtractUnallocAction extends AbstractAction{
                 }                
                 ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc);
                 uw.execute();
-            }catch (TskCoreException tce) {
-                logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce);
-            }
         } else {
-            logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + vol.getId() + ", but its list of unallocated files was empty or null");
+            logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + VolumeId + ", but its list of unallocated files was empty or null");
         }
     }
 
@@ -183,7 +187,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * If the volume has no FileSystem, then it will call this method to return the single instance of unallocated space.
          * @param lf the LayoutFile the visitor encountered
-         * @return A list of size 1
+         * @return A list of size 1, returns null if it fails
          */
         @Override
         public List visit(final org.sleuthkit.datamodel.LayoutFile lf) {
@@ -197,7 +201,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * If the visitor finds a FileSystem, it will filter the results for directories and return on the Root Dir.
          * @param fs the FileSystem the visitor encountered
-         * @return A list containing the layout files from subsequent Visits()
+         * @return A list containing the layout files from subsequent Visits(), returns null if it fails
          */
         @Override
         public List visit(FileSystem fs) {
@@ -216,7 +220,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * LayoutDirectory has all the Layout(Unallocated) files
          * @param ld LayoutDirectory the visitor encountered
-         * @return A list containing all the LayoutFile in ld
+         * @return A list containing all the LayoutFile in ld, returns null if it fails
          */
         @Override
         public List visit(LayoutDirectory ld){
@@ -227,7 +231,7 @@ public final class ExtractUnallocAction extends AbstractAction{
                 }
                 return lflst;
             } catch(TskCoreException tce){
-                logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory in " + vol.getId(), tce);
+                logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory", tce);
             }
             return null;
         }
@@ -235,7 +239,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * The only time this visitor should ever encounter a directory is when parsing over Root
          * @param dir the directory this visitor encountered
-         * @return A list containing LayoutFiles encountered during subsequent Visits()
+         * @return A list containing LayoutFiles encountered during subsequent Visits(), returns null if it fails
          */
         @Override
         public List visit(Directory dir) {
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java
index c539f9b0de..537b127da2 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java
@@ -44,11 +44,7 @@ import org.sleuthkit.datamodel.FileSystem;
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.Volume;
 
-/**
- * 
- *
- * @author jantonius
- */
+
 class ShowDetailActionVisitor extends ContentVisitor.Default> {
     
     private static ShowDetailActionVisitor instance = new ShowDetailActionVisitor();
@@ -231,11 +227,32 @@ class ShowDetailActionVisitor extends ContentVisitor.Default visit(final Volume vol) {
-        final String title = "Volume Details";
+        List lst = new ArrayList();
+        lst.add(new VolumeDetailsAction("Volume Details", vol));
+        lst.add(new ExtractUnallocAction("Extract Unallocated Space to Single File", vol));
+        return lst;
+    }
+       
 
-        return Collections.singletonList(new AbstractAction(title) {
+    @Override
+    protected List defaultVisit(Content di) {
+        return new ArrayList();
+    }
+}
 
-            @Override
+
+ class VolumeDetailsAction extends AbstractAction{
+     
+     String title;
+     Volume vol;
+     
+     VolumeDetailsAction(String title, Volume vol){
+         super(title);
+         this.title = title;
+         this.vol = vol;
+     }
+     
+                 @Override
             public void actionPerformed(ActionEvent e) {
                 Logger.noteAction(ShowDetailActionVisitor.class);
 
@@ -289,11 +306,4 @@ class ShowDetailActionVisitor extends ContentVisitor.Default defaultVisit(Content di) {
-        return new ArrayList();
-    }
-}
\ No newline at end of file
+ }
\ No newline at end of file

From 9ab004802a12c58053eb039e49bc2409ad2ca04b Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 12:18:45 -0500
Subject: [PATCH 09/13] Download Path column now correctly shows location of
 the the local file.

---
 .../src/org/sleuthkit/autopsy/recentactivity/Firefox.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
index 74d6c46d6e..c6995577a8 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
@@ -282,8 +282,8 @@ public class Firefox extends Extract implements IngestModuleImage {
                         //TODO Revisit usage of deprecated constructor as per TSK-583
                         //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "RecentActivity", "Last Visited", (Long.valueOf(result.get("startTime").toString()))));
                         bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID(), "RecentActivity", (Long.valueOf(result.get("startTime").toString()))));
-                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", Util.findID(urldecodedtarget)));
-                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", urldecodedtarget));
+                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", Util.findID(urldecodedtarget))); 
+                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", ((result.get("target").toString() != null) ? result.get("target").toString() : "")));
                         bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), "RecentActivity", "FireFox"));
                         bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID(), "RecentActivity", (Util.extractDomain((result.get("source").toString() != null) ? result.get("source").toString() : ""))));
                         this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, FFSqlitedb.get(j), bbattributes);

From 3f4b1fe476267d71efb1e1be3071224a92088d19 Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 13:30:40 -0500
Subject: [PATCH 10/13] CurrentlyProcessing tags will now vanish when AddImage
 completes.

---
 .../casemodule/AddImageVisualPanel2.java      |  8 +++++++
 .../casemodule/AddImageWizardPanel3.java      | 22 ++++++++++++-------
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java
index f42b418824..333f4e9db4 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java
@@ -111,6 +111,14 @@ final class AddImageVisualPanel2 extends JPanel {
     public void changeCurrentDir(String dir){
         this.TextArea_CurrentDirectory.setText(dir);
     }
+    
+    /**
+     * Sets the CurrentlyProcessing tag and text area to be invisible
+     */
+    public void setProcessInvis(){
+        this.Label_CurrentDirectory_Static.setVisible(false);
+        this.TextArea_CurrentDirectory.setVisible(false);
+    }
 
     
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java
index 6ffa8fd944..aa5f04b690 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java
@@ -226,25 +226,31 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel {
                 while(prog.getValue() < 100 || prog.isIndeterminate()){ //TODO Rely on state variable in AddImgTask class
                     
                     EventQueue.invokeLater(new Runnable() {
-
                         @Override
-                        public void run() {        
-                             wiz.changeCurrentDir(proc.currentDirectory());
+                        public void run() {
+                            wiz.changeCurrentDir(proc.currentDirectory());
                         }
-                        
-                  
                     });
-                            
+
                     Thread.sleep(2 * 1000);
                 }
                 return 1;
-            }
-            catch(InterruptedException ie){
+            } catch (InterruptedException ie) {
                 return -1;
             }
         }
+
+        /**
+         * When done, set the Wizards processing tags to be invisible
+         */
+        @Override
+        protected void done() {
+            wiz.setProcessInvis();
+        }
     }
 
+
+
     /**
      * Thread that will make the JNI call to ingest the image.
      */

From 42523e99258c07e005dd542b7af54ebc7da98ea0 Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Fri, 30 Nov 2012 17:34:38 -0500
Subject: [PATCH 11/13] update to use non-static hasher

---
 .../sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java  | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java
index eb3d241288..2a59d671df 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java
@@ -69,6 +69,8 @@ public class HashDbIngestModule implements IngestModuleAbstractFile {
     static long lookuptime = 0;
     private Map knownBadSets = new HashMap();
     private HashDbManagementPanel panel;
+    
+    private final Hash hasher = new Hash();
 
     private HashDbIngestModule() {
         count = 0;
@@ -332,7 +334,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile {
                     String md5Hash = fsContent.getMd5Hash();
                     if (md5Hash == null || md5Hash.isEmpty()) {
                         long calcstart = System.currentTimeMillis();
-                        md5Hash = Hash.calculateMd5(fsContent);
+                        md5Hash = hasher.calculateMd5(fsContent);
                         calctime += (System.currentTimeMillis() - calcstart);
                     }
                     TskData.FileKnown status = TskData.FileKnown.UKNOWN;
@@ -374,7 +376,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile {
                     String md5Hash = fsContent.getMd5Hash();
                     if (md5Hash == null || md5Hash.isEmpty()) {
                         long calcstart = System.currentTimeMillis();
-                        Hash.calculateMd5(fsContent);
+                        hasher.calculateMd5(fsContent);
                         calctime += (System.currentTimeMillis() - calcstart);
                     }
                     ret = ProcessResult.OK;

From f2155f3aa71201f54a15dfc818e2d37ae8c5c56c Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Fri, 30 Nov 2012 17:49:57 -0500
Subject: [PATCH 12/13] Case closing: close top components before sending out
 events when case is changed, for a cleaner shutdown

---
 Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 5d389844b4..02b31d6a6e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -173,11 +173,13 @@ public class Case {
 
         String oldCaseName = oldCase != null ? oldCase.name : "";
 
+        doCaseChange(null); //closes windows, etc
         pcs.firePropertyChange(CASE_CURRENT_CASE, oldCase, null);
-        doCaseChange(null);
+        
 
-        pcs.firePropertyChange(CASE_NAME, oldCaseName, "");
         doCaseNameChange("");
+        pcs.firePropertyChange(CASE_NAME, oldCaseName, "");
+        
 
 
 
@@ -827,14 +829,16 @@ public class Case {
                 Case.runAddImageAction();
             }
         } else { // case is closed
+            // close all top components first
+            CoreComponentControl.closeCoreWindows();
+            
             // disable these menus
             CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
             CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
             CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
             CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
 
-            // close all top components
-            CoreComponentControl.closeCoreWindows();
+            
 
             Frame f = WindowManager.getDefault().getMainWindow();
             f.setTitle(Case.getAppName()); // set the window name to just application name

From b38c7dd80e43f5c192fdb3e20fbfef51651458fd Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Mon, 3 Dec 2012 16:36:39 -0500
Subject: [PATCH 13/13] updated news

---
 NEWS.txt | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/NEWS.txt b/NEWS.txt
index faafb83b3e..7b5223753b 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -3,11 +3,16 @@
 New features:
 
 Improvements:
-- Add Image Wizard - better work-flow, better device size reporting
-- File Ingest: reduced file queuing time and memory usage
+- File Ingest: minimized file queuing time and memory usage
+- Add Image Wizard - better work-flow, better device size reporting, info on currently processed directory
+- Added extraction of all unallocated blocks (from volume, image) as a single file
+- Reporting improvements: reorganized columns, sorted by 1st column, added logo to html report
 
 Bugfixes:
 - fixed periodic keyword search during ingest, when it'd run max. 2 times only
+- fixed Downloads "target" in Recent Activity
+- fixed missing hash and keyword search hits in reports
+
 
 ---------------- VERSION 3.0.1 --------------