diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp
index 56b2d758106400ae7f574ef3ad438078a9db2e86..969d87ba117d3a9b4959ad27feb27c116d42b62f 100644
--- a/bindings/java/jni/auto_db_java.cpp
+++ b/bindings/java/jni/auto_db_java.cpp
@@ -110,16 +110,11 @@ TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) {
         return TSK_ERR;
     }
 
-    m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;)J");
+    m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;JJJ)J");
     if (m_addFileMethodID == NULL) {
         return TSK_ERR;
     }
 
-    m_getParentIdMethodID = m_jniEnv->GetMethodID(m_callbackClass, "findParentObjId", "(JJLjava/lang/String;Ljava/lang/String;)J");
-    if (m_getParentIdMethodID == NULL) {
-        return TSK_ERR;
-    }
-
     m_addUnallocParentMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addUnallocFsBlockFilesParent", "(JLjava/lang/String;)J");
     if (m_addUnallocParentMethodID == NULL) {
         return TSK_ERR;
@@ -427,7 +422,7 @@ TskAutoDbJava::addFsFile(TSK_FS_FILE* fs_file,
     if (fs_file->name == NULL)
         return TSK_ERR;
 
-    // Find the object id for the parent folder.
+    // The object id for the parent folder. Will stay as zero if not the root folder
     int64_t parObjId = 0;
 
     // Root directory's parent should be the file system object.
@@ -436,16 +431,10 @@ TskAutoDbJava::addFsFile(TSK_FS_FILE* fs_file,
         ((fs_file->name->name == NULL) || (strlen(fs_file->name->name) == 0))) {
         // File is in the root directory
         parObjId = fsObjId;
-    } else {
-        // Look up parent object ID
-        parObjId = findParObjId(fs_file, path, fsObjId);
-        if (parObjId == -1) {
-            return TSK_ERR;
-        }
     }
 
     // Add the file to the database
-    return addFile(fs_file, fs_attr, path, fsObjId, parObjId, objId, dataSourceObjId);
+    return addFile(fs_file, fs_attr, path, fsObjId, parObjId, dataSourceObjId);
 }
 
 /**
@@ -471,61 +460,15 @@ void extractExtension(char *name, char *extension) {
 }
 
 /**
-* Store info about a directory in a complex map structure as a cache for the
-* files who are a child of this directory and want to know its object id.
-*
-* @param fsObjId fs id of this directory
-* @param fs_file File for the directory to store
-* @param path Full path (parent and this file) of the directory
-* @param objId object id of the directory
-*/
-void TskAutoDbJava::storeObjId(const int64_t& fsObjId, const TSK_FS_FILE* fs_file, const char* path, const int64_t& objId)
-{
-    // skip the . and .. entries
-    if ((fs_file->name) && (fs_file->name->name) && (TSK_FS_ISDOT(fs_file->name->name)))
-    {
-        return;
-    }
-
-    uint32_t seq;
-    uint32_t path_hash = hash((const unsigned char *)path);
-
-    /* NTFS uses sequence, otherwise we hash the path. We do this to map to the
-    * correct parent folder if there are two from the root dir that eventually point to
-    * the same folder (one deleted and one allocated) or two hard links. */
-    if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype)) {
-        /* Use the sequence stored in meta (which could be one larger than the name value
-        * if the directory is deleted. We do this because the par_seq gets added to the
-        * name structure when it is added to the directory based on teh value stored in
-        * meta. */
-        seq = fs_file->meta->seq;
-    }
-    else {
-        seq = path_hash;
-    }
-
-    map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > >& fsMap = m_parentDirIdCache[fsObjId];
-    if (fsMap.count(fs_file->name->meta_addr) == 0) {
-        fsMap[fs_file->name->meta_addr][seq][path_hash] = objId;
-    }
-    else {
-        map<uint32_t, map<uint32_t, int64_t> >& fileMap = fsMap[fs_file->name->meta_addr];
-        if (fileMap.count(seq) == 0) {
-            fileMap[seq][path_hash] = objId;
-        }
-    }
-}
-
-
-/**
-* Adds a file and its associated slack file to database. Object ID for new file stored in objId.
+* Adds a file and its associated slack file to database.
+* Does not learn object ID for new files, and files may 
+* not be added to the database immediately.
 *
 * @param fs_file
 * @param fs_attr
 * @param path      File path
 * @param fsObjId   Object ID of the file system
-* @param parObjId  Parent object ID
-* @param objId     Object ID of new file
+* @param parObjId  Parent object ID if known, 0 otherwise
 * @param dataSourceObjId  Object ID of the data source
 * @returns TSK_ERR on error, TSK_OK on success
 */
@@ -533,7 +476,7 @@ TSK_RETVAL_ENUM
 TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
     const TSK_FS_ATTR* fs_attr, const char* path,
     int64_t fsObjId, int64_t parObjId,
-    int64_t& objId, int64_t dataSourceObjId)
+    int64_t dataSourceObjId)
 {
     time_t mtime = 0;
     time_t crtime = 0;
@@ -543,6 +486,7 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
     int meta_type = 0;
     int meta_flags = 0;
     int meta_mode = 0;
+    int meta_seq = 0;
     int gid = 0;
     int uid = 0;
     int type = TSK_FS_ATTR_TYPE_NOT_FOUND;
@@ -561,6 +505,7 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
         meta_mode = fs_file->meta->mode;
         gid = fs_file->meta->gid;
         uid = fs_file->meta->uid;
+        meta_seq = fs_file->meta->seq;
     }
 
     size_t attr_nlen = 0;
@@ -616,9 +561,22 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
     jstring namej = m_jniEnv->NewStringUTF(name);
     jstring pathj = m_jniEnv->NewStringUTF(escaped_path);
     jstring extj = m_jniEnv->NewStringUTF(extension);
+
+    /* NTFS uses sequence, otherwise we hash the path. We do this to map to the
+    * correct parent folder if there are two from the root dir that eventually point to
+    * the same folder (one deleted and one allocated) or two hard links. */
+    jlong par_seqj;
+    if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype))
+    {
+        par_seqj = fs_file->name->par_seq;
+    }
+    else {
+        par_seqj = -1;
+    }
+    TSK_INUM_T par_meta_addr = fs_file->name->par_addr;
  
     // Add the file to the database
-    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
+    jlong ret_val = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
         parObjId, fsObjId,
         dataSourceObjId,
         TSK_DB_FILES_TYPE_FS,
@@ -628,19 +586,13 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
         size,
         (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime,
         meta_mode, gid, uid, 
-        pathj, extj);
-    objId = (int64_t)objIdj;
+        pathj, extj, 
+        (uint64_t)meta_seq, par_meta_addr, par_seqj);
 
-    if (objId < 0) {
+    if (ret_val < 0) {
         return TSK_ERR;
     }
 
-    // If dir, update parent ID cache
-    if (TSK_FS_IS_DIR_META(meta_type)){
-        std::string fullPath = std::string(path) + fs_file->name->name;
-        storeObjId(fsObjId, fs_file, fullPath.c_str(), objId);
-    }
-
     // Add entry for the slack space.
     // Current conditions for creating a slack file:
     //   - File name is not empty, "." or ".."
@@ -662,7 +614,7 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
         TSK_OFF_T slackSize = fs_attr->nrd.allocsize - fs_attr->nrd.initsize;
 
         // Add slack file to database
-        jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
+        jlong ret_val = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
             parObjId, fsObjId,
             dataSourceObjId,
             TSK_DB_FILES_TYPE_SLACK,
@@ -672,10 +624,10 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file,
             slackSize,
             (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime,
             meta_mode, gid, uid, // md5TextPtr, known,
-            pathj, slackExtj);
-        int64_t slackObjId = (int64_t)objIdj;
+            pathj, slackExtj, 
+            (uint64_t)meta_seq, par_meta_addr, par_seqj);
 
-        if (slackObjId < 0) {
+        if (ret_val < 0) {
             return TSK_ERR;
         }
     }
@@ -875,161 +827,6 @@ TskAutoDbJava::addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objI
     return TSK_OK;
 }
 
-/**
-* Return a hash of the passed in string. We use this
-* for full paths.
-* From: http://www.cse.yorku.ca/~oz/hash.html
-*/
-uint32_t 
-TskAutoDbJava::hash(const unsigned char* str)
-{
-    uint32_t hash = 5381;
-    int c;
-
-    while ((c = *str++)) {
-        // skip slashes -> normalizes leading/ending/double slashes
-        if (c == '/')
-            continue;
-        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
-    }
-
-    return hash;
-}
-
-/*
-* Utility method to break up path into parent folder and folder/file name.
-* @param path Path of folder that we want to analyze
-* @param ret_parent_path pointer to parent path (begins and ends with '/')
-* @param ret_name pointer to final folder/file name
-* @returns 0 on success, 1 on error
-*/
-bool 
-TskAutoDbJava::getParentPathAndName(const char *path, const char **ret_parent_path, const char **ret_name) {
-    // Need to break up 'path' in to the parent folder to match in 'parent_path' and the folder 
-    // name to match with the 'name' column in tsk_files table
-
-    // reset all arrays
-    parent_name[0] = '\0';
-    parent_path[0] = '\0';
-
-    size_t path_len = strlen(path);
-    if (path_len >= MAX_PATH_LENGTH_JAVA_DB_LOOKUP) {
-        tsk_error_reset();
-        tsk_error_set_errno(TSK_ERR_AUTO_DB);
-        tsk_error_set_errstr("TskDb::getParentPathAndName: Path is too long. Length = %zd, Max length = %d", path_len, MAX_PATH_LENGTH_JAVA_DB_LOOKUP);
-        // assign return values to pointers
-        *ret_parent_path = "";
-        *ret_name = "";
-        return 1;
-    }
-
-    // check if empty path or just "/" were passed in
-    if (path_len == 0 || (strcmp(path, "/") == 0)) {
-        *ret_name = "";
-        *ret_parent_path = "/";
-        return 0;
-    }
-
-
-    // step 1, copy everything into parent_path and clean it up
-    // add leading slash if its not in input.  
-    if (path[0] != '/') {
-        sprintf(parent_path, "%s", "/");
-    }
-
-    strncat(parent_path, path, MAX_PATH_LENGTH_JAVA_DB_LOOKUP);
-
-    // remove trailing slash
-    if (parent_path[strlen(parent_path) - 1] == '/') {
-        parent_path[strlen(parent_path) - 1] = '\0';
-    }
-
-    // replace all non-UTF8 characters
-    tsk_cleanupUTF8(parent_path, '^');
-
-    // Step 2, move the final folder/file to parent_file
-
-    // Find the last '/' 
-    char *chptr = strrchr(parent_path, '/');
-    if (chptr) {
-        // character found in the string
-        size_t position = chptr - parent_path;
-
-        sprintf(parent_name, "%s", chptr + 1);  // copy everything after slash into parent_name
-        *ret_name = parent_name;
-
-        parent_path[position + 1] = '\0';   // add terminating null after last "/"
-        *ret_parent_path = parent_path;
-    }
-    else {
-        // "/" character not found. the entire path is parent file name. parent path is "/"
-        *ret_name = parent_path;
-        *ret_parent_path = "/";
-    }
-    return 0;
-}
-
-/**
-* Find parent object id of TSK_FS_FILE. Use local cache map, if not found, fall back to SQL
-* @param fs_file file to find parent obj id for
-* @param parentPath Path of parent folder that we want to match
-* @param fsObjId fs id of this file
-* @returns parent obj id ( > 0), -1 on error
-*/
-int64_t 
-TskAutoDbJava::findParObjId(const TSK_FS_FILE* fs_file, const char* parentPath, const int64_t& fsObjId)
-{
-    uint32_t seq;
-    uint32_t path_hash = hash((const unsigned char *)parentPath);
-
-    /* NTFS uses sequence, otherwise we hash the path. We do this to map to the
-    * correct parent folder if there are two from the root dir that eventually point to
-    * the same folder (one deleted and one allocated) or two hard links. */
-    if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype))
-    {
-        seq = fs_file->name->par_seq;
-    }
-    else
-    {
-        seq = path_hash;
-    }
-
-    //get from cache by parent meta addr, if available
-    map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > >& fsMap = m_parentDirIdCache[fsObjId];
-    if (fsMap.count(fs_file->name->par_addr) > 0)
-    {
-        map<uint32_t, map<uint32_t, int64_t> >& fileMap = fsMap[fs_file->name->par_addr];
-        if (fileMap.count(seq) > 0) {
-            map<uint32_t, int64_t>& pathMap = fileMap[seq];
-            if (pathMap.count(path_hash) > 0) {
-                return pathMap[path_hash];
-            }
-        }
-    }
-
-    // Need to break up 'path' in to the parent folder to match in 'parent_path' and the folder 
-    // name to match with the 'name' column in tsk_files table
-    const char *parent_name = "";
-    const char *parent_path = "";
-    if (getParentPathAndName(parentPath, &parent_path, &parent_name)) {
-        return -1;
-    }
-
-    jstring jpath = m_jniEnv->NewStringUTF(parent_path);
-    jstring jname = m_jniEnv->NewStringUTF(parent_name);
-
-    // Look up in the database
-    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_getParentIdMethodID,
-        fs_file->name->par_addr, fsObjId, jpath, jname);
-    int64_t objId = (int64_t)objIdj;
-
-    if (objId < 0) {
-        return -1;
-    }
-
-    return objId;
-}
-
 /**
 * Adds a new volume that will hold the unallocated blocks for the pool.
 *
diff --git a/bindings/java/jni/auto_db_java.h b/bindings/java/jni/auto_db_java.h
index 85a9dc69eb1d006032ef6a2ba7470dfd563f95ae..e6c5e68dd7217d784695749cf53c24df090db75d 100644
--- a/bindings/java/jni/auto_db_java.h
+++ b/bindings/java/jni/auto_db_java.h
@@ -143,16 +143,6 @@ class TskAutoDbJava :public TskAuto {
     std::map<int64_t, int64_t> m_poolOffsetToParentId;
     std::map<int64_t, int64_t> m_poolOffsetToVsId;
 
-    // Used to look up object IDs for files
-    #define MAX_PATH_LENGTH_JAVA_DB_LOOKUP 2048
-    char parent_name[MAX_PATH_LENGTH_JAVA_DB_LOOKUP];
-    char parent_path[MAX_PATH_LENGTH_JAVA_DB_LOOKUP + 2]; // +2 is for leading slash and trailing slash
-    map<int64_t, map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > > > m_parentDirIdCache; //maps a file system ID to a map, which maps a directory file system meta address to a map, which maps a sequence ID to a map, which maps a hash of a path to its object ID in the database
-    int64_t findParObjId(const TSK_FS_FILE* fs_file, const char* parentPath, const int64_t& fsObjId);
-    bool getParentPathAndName(const char *path, const char **ret_parent_path, const char **ret_name);
-    void storeObjId(const int64_t& fsObjId, const TSK_FS_FILE* fs_file, const char* path, const int64_t& objId);
-    uint32_t hash(const unsigned char* str);
-
     // JNI data
     JNIEnv * m_jniEnv = NULL;
     jclass m_callbackClass = NULL;
@@ -164,7 +154,6 @@ class TskAutoDbJava :public TskAuto {
     jmethodID m_addPoolMethodID = NULL;
     jmethodID m_addFileSystemMethodID = NULL;
     jmethodID m_addFileMethodID = NULL;
-    jmethodID m_getParentIdMethodID = NULL;
     jmethodID m_addUnallocParentMethodID = NULL;
     jmethodID m_addLayoutFileMethodID = NULL;
     jmethodID m_addLayoutFileRangeMethodID = NULL;
@@ -228,7 +217,7 @@ class TskAutoDbJava :public TskAuto {
     TSK_RETVAL_ENUM addFile(TSK_FS_FILE* fs_file,
         const TSK_FS_ATTR* fs_attr, const char* path,
         int64_t fsObjId, int64_t parObjId,
-        int64_t& objId, int64_t dataSourceObjId);
+        int64_t dataSourceObjId);
     TSK_RETVAL_ENUM addFileWithLayoutRange(const TSK_DB_FILES_TYPE_ENUM dbFileType, const int64_t parentObjId,
         const int64_t fsObjId, const uint64_t size,
         vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
index a091dcfe60676ce95ef0f050f287aa26b4715d90..7cbe2b0f91f40d0661f6a38ef671c67d87d92d50 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
@@ -180,11 +180,14 @@ private void createFileTables(Statement stmt) throws SQLException {
 				+ "time_zone TEXT NOT NULL, acquisition_details TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
 
 		stmt.execute("CREATE TABLE tsk_fs_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
 				+ "img_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, fs_type INTEGER NOT NULL, "
 				+ "block_size " + dbQueryHelper.getBigIntType() + " NOT NULL, "
 				+ "block_count " + dbQueryHelper.getBigIntType() + " NOT NULL, root_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, "
 				+ "first_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, last_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, "
-				+ "display_name TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
+				+ "display_name TEXT, " 
+				+ "FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE, "
+				+ "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)");
 
 		stmt.execute("CREATE TABLE tsk_files (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
 				+ "fs_obj_id " + dbQueryHelper.getBigIntType() + ", data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, "
diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
index 2e9bc72106ebea19430ba617dee1f9a232a306f3..b65ad77b136adda0578a7bce53ed9b2f56945902 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
@@ -18,8 +18,12 @@
  */
 package org.sleuthkit.datamodel;
 
+import org.apache.commons.lang3.StringUtils;
+import java.util.List;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
@@ -38,6 +42,12 @@ class JniDbHelper {
     private CaseDbTransaction trans = null;
     
     private final Map<Long, Long> fsIdToRootDir = new HashMap<>();
+    private final Map<Long, TskData.TSK_FS_TYPE_ENUM> fsIdToFsType = new HashMap<>();
+    private final Map<ParentCacheKey, Long> parentDirCache = new HashMap<>();
+    
+    private static final long BATCH_FILE_THRESHOLD = 500;
+    private final List<FileInfo> batchedFiles = new ArrayList<>();
+    private final List<LayoutRangeInfo> batchedLayoutRanges = new ArrayList<>();
     
     JniDbHelper(SleuthkitCase caseDb) {
         this.caseDb = caseDb;
@@ -45,34 +55,47 @@ class JniDbHelper {
     }
     
     /**
-     * Start the add image transaction
+     * Start a transaction
      * 
      * @throws TskCoreException 
      */
-    void beginTransaction() throws TskCoreException {
+    private void beginTransaction() throws TskCoreException {
         trans = caseDb.beginTransaction();
+        trans.acquireSingleUserCaseWriteLock();
     }
     
     /**
-     * Commit the add image transaction
+     * Commit the current transaction
      * 
      * @throws TskCoreException 
      */
-    void commitTransaction() throws TskCoreException {
+    private void commitTransaction() throws TskCoreException {
         trans.commit();
         trans = null;
     }
     
     /**
-     * Revert the add image transaction
-     * 
-     * @throws TskCoreException 
+     * Revert the current transaction
      */
-    void revertTransaction() throws TskCoreException {
-        trans.rollback();
-        trans = null;
+    private void revertTransaction() {
+        try {
+            if (trans != null) {
+                trans.rollback();
+                trans = null;
+            }
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error rolling back transaction", ex);
+        }
     }        
     
+    /**
+     * Add any remaining files to the database.
+     */
+    void finish() {
+        addBatchedFilesToDb();
+        addBatchedLayoutRangesToDb();
+    }
+    
     /**
      * Add a new image to the database.
      * Intended to be called from the native code during the add image process.
@@ -91,14 +114,18 @@ void revertTransaction() throws TskCoreException {
      */
     long addImageInfo(int type, long ssize, String timezone, 
             long size, String md5, String sha1, String sha256, String deviceId, 
-            String collectionDetails) {
+            String collectionDetails) {    
         try {
-            return caseDb.addImageJNI(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), ssize, size,
+            beginTransaction();
+            long objId = caseDb.addImageJNI(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), ssize, size,
                     timezone, md5, sha1, sha256, deviceId, collectionDetails, trans);
+            commitTransaction();
+            return objId;
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding image to the database", ex);
+            revertTransaction();
             return -1;
-        }
+        } 
     }
     
     /**
@@ -113,11 +140,14 @@ long addImageInfo(int type, long ssize, String timezone,
      */
     int addImageName(long objId, String name, long sequence) {
         try {
+            beginTransaction();
             caseDb.addImageNameJNI(objId, name, sequence, trans);
+            commitTransaction();
             return 0;
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding image name to the database - image obj ID: " + objId + ", image name: " + name
                     + ", sequence: " + sequence, ex);
+            revertTransaction();
             return -1;
         }
     }
@@ -135,11 +165,14 @@ int addImageName(long objId, String name, long sequence) {
      */
     long addVsInfo(long parentObjId, int vsType, long imgOffset, long blockSize) {
         try {
+            beginTransaction();
             VolumeSystem vs = caseDb.addVolumeSystem(parentObjId, TskData.TSK_VS_TYPE_ENUM.valueOf(vsType), imgOffset, blockSize, trans);
+            commitTransaction();
             return vs.getId();
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding volume system to the database - parent obj ID: " + parentObjId 
                     + ", image offset: " + imgOffset, ex);
+            revertTransaction();
             return -1;
         }
     }
@@ -160,11 +193,14 @@ long addVsInfo(long parentObjId, int vsType, long imgOffset, long blockSize) {
     long addVolume(long parentObjId, long addr, long start, long length, String desc,
             long flags) {
         try {
+            beginTransaction();
             Volume vol = caseDb.addVolume(parentObjId, addr, start, length, desc, flags, trans);
+            commitTransaction();
             return vol.getId();
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding volume to the database - parent object ID: " + parentObjId
                 + ", addr: " + addr, ex);
+            revertTransaction();
             return -1;
         }
     }
@@ -180,10 +216,13 @@ long addVolume(long parentObjId, long addr, long start, long length, String desc
      */
     long addPool(long parentObjId, int poolType) {
         try {
+            beginTransaction();
             Pool pool = caseDb.addPool(parentObjId, TskData.TSK_POOL_TYPE_ENUM.valueOf(poolType), trans);
+            commitTransaction();
             return pool.getId();
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding pool to the database - parent object ID: " + parentObjId, ex);
+            revertTransaction();
             return -1;
         }
     }
@@ -206,21 +245,26 @@ long addPool(long parentObjId, int poolType) {
     long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize, long blockCount,
             long rootInum, long firstInum, long lastInum) {
         try {
+            beginTransaction();
             FileSystem fs = caseDb.addFileSystem(parentObjId, imgOffset, TskData.TSK_FS_TYPE_ENUM.valueOf(fsType), blockSize, blockCount,
                     rootInum, firstInum, lastInum, null, trans);
+            commitTransaction();
+            fsIdToFsType.put(fs.getId(), TskData.TSK_FS_TYPE_ENUM.valueOf(fsType));
             return fs.getId();
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding file system to the database - parent object ID: " + parentObjId
                     + ", offset: " + imgOffset, ex);
+            revertTransaction();
             return -1;
         }
     }
 
     /**
-     * Add a file to the database. 
+     * Add a file to the database.
+     * File inserts are batched so the file may not be added immediately.
      * Intended to be called from the native code during the add image process.
      * 
-     * @param parentObjId     The parent of the file.
+     * @param parentObjId     The parent of the file if known or 0 if unknown.
      * @param fsObjId         The object ID of the file system.
      * @param dataSourceObjId The data source object ID.
      * @param fsType    The type.
@@ -228,7 +272,7 @@ long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize,
      * @param attrId    The type id given to the file by the file  system.
      * @param name      The name of the file.
      * @param metaAddr  The meta address of the file.
-     * @param metaSeq   The meta sequence number of the file.
+     * @param metaSeq   The meta sequence number of the file from fs_file->name->meta_seq.
      * @param dirType   The type of the file, usually as reported in
      *                     the name structure of the file system. 
      * @param metaType  The type of the file, usually as reported in
@@ -249,8 +293,11 @@ long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize,
      * @param known     The file known status.
      * @param escaped_path The escaped path to the file.
      * @param extension    The file extension.
+     * @param seq         The sequence number from fs_file->meta->seq. 
+     * @param parMetaAddr The metadata address of the parent
+     * @param parSeq      The parent sequence number if NTFS, -1 otherwise.
      * 
-     * @return The object ID of the new file or -1 if an error occurred
+     * @return 0 if successful, -1 if not
      */
     long addFile(long parentObjId, 
         long fsObjId, long dataSourceObjId,
@@ -261,9 +308,11 @@ long addFile(long parentObjId,
         long size,
         long crtime, long ctime, long atime, long mtime,
         int meta_mode, int gid, int uid,
-        String escaped_path, String extension) {
-        try {
-            long objId = caseDb.addFileJNI(parentObjId, 
+        String escaped_path, String extension, 
+        long seq, long parMetaAddr, long parSeq) {
+        
+        // Add the new file to the list
+        batchedFiles.add(new FileInfo(parentObjId,
                 fsObjId, dataSourceObjId,
                 fsType,
                 attrType, attrId, name,
@@ -272,21 +321,105 @@ long addFile(long parentObjId,
                 size,
                 crtime, ctime, atime, mtime,
                 meta_mode, gid, uid,
-                null, TskData.FileKnown.UNKNOWN,
-                escaped_path, extension, 
-                false, trans);
-            
-            // If we're adding the root directory for the file system, cache it
-            if (parentObjId == fsObjId) {
-                fsIdToRootDir.put(fsObjId, objId);
+                escaped_path, extension,
+                seq, parMetaAddr, parSeq));
+        
+        // Add the current files to the database if we've exceeded the threshold or if we
+        // have the root folder.
+        if ((fsObjId == parentObjId)
+                || (batchedFiles.size() > BATCH_FILE_THRESHOLD)) {
+            return addBatchedFilesToDb();
+        }
+        return 0;
+    }
+    
+    /**
+     * Add the current set of files to the database.
+     * 
+     * @return 0 if successful, -1 if not
+     */
+    private long addBatchedFilesToDb() {
+        try {
+            beginTransaction();
+            for (FileInfo fileInfo : batchedFiles) {
+                long computedParentObjId = fileInfo.parentObjId;
+                try {
+                    // If we weren't given the parent object ID, look it up
+                    if (fileInfo.parentObjId == 0) {
+                        computedParentObjId = getParentObjId(fileInfo);
+                    }
+
+                    long objId = caseDb.addFileJNI(computedParentObjId, 
+                        fileInfo.fsObjId, fileInfo.dataSourceObjId,
+                        fileInfo.fsType,
+                        fileInfo.attrType, fileInfo.attrId, fileInfo.name,
+                        fileInfo.metaAddr, fileInfo.metaSeq,
+                        fileInfo.dirType, fileInfo.metaType, fileInfo.dirFlags, fileInfo.metaFlags,
+                        fileInfo.size,
+                        fileInfo.crtime, fileInfo.ctime, fileInfo.atime, fileInfo.mtime,
+                        fileInfo.meta_mode, fileInfo.gid, fileInfo.uid,
+                        null, TskData.FileKnown.UNKNOWN,
+                        fileInfo.escaped_path, fileInfo.extension, 
+                        false, trans);
+
+                    // If we're adding the root directory for the file system, cache it
+                    if (fileInfo.parentObjId == fileInfo.fsObjId) {
+                        fsIdToRootDir.put(fileInfo.fsObjId, objId);
+                    }
+
+                    // If the file is a directory, cache the object ID
+                    if ((fileInfo.metaType == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()
+                            || (fileInfo.metaType == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR.getValue()))
+                            && (fileInfo.name != null)
+                            && ! fileInfo.name.equals(".")
+                            && ! fileInfo.name.equals("..")) {
+                        String dirName = fileInfo.escaped_path + fileInfo.name;
+                        ParentCacheKey key = new ParentCacheKey(fileInfo.fsObjId, fileInfo.metaAddr, fileInfo.seq, dirName);
+                        parentDirCache.put(key, objId);
+                    }
+                } catch (TskCoreException ex) {
+                    logger.log(Level.SEVERE, "Error adding file to the database - parent object ID: " + computedParentObjId
+                            + ", file system object ID: " + fileInfo.fsObjId + ", name: " + fileInfo.name, ex);
+                    revertTransaction();
+                    return -1;
+                }
             }
-            return objId;
+            commitTransaction();
         } catch (TskCoreException ex) {
-            logger.log(Level.SEVERE, "Error adding file to the database - parent object ID: " + parentObjId
-                    + ", file system object ID: " + fsObjId + ", name: " + name, ex);
+            logger.log(Level.SEVERE, "Error adding batched files to database", ex);
+            revertTransaction();
             return -1;
         }
+        batchedFiles.clear();
+        return 0;
     }
+	
+	/**
+	 * Look up the parent object ID for a file using the cache or the database.
+	 * 
+	 * @param fileInfo The file to find the parent of
+	 * 
+	 * @return Parent object ID
+	 * 
+	 * @throws TskCoreException 
+	 */
+	private long getParentObjId(FileInfo fileInfo) throws TskCoreException {
+		// Remove the final slash from the path unless we're in the root folder
+		String parentPath = fileInfo.escaped_path;
+		if(parentPath.endsWith("/") && ! parentPath.equals("/")) {
+			parentPath =  parentPath.substring(0, parentPath.lastIndexOf('/'));
+		}
+
+		// Look up the parent
+		ParentCacheKey key = new ParentCacheKey(fileInfo.fsObjId, fileInfo.parMetaAddr, fileInfo.parSeq, parentPath);
+		if (parentDirCache.containsKey(key)) {
+			return parentDirCache.get(key);
+		} else {
+			// The parent wasn't found in the cache so do a database query
+			java.io.File parentAsFile = new java.io.File(parentPath);
+			return caseDb.findParentObjIdJNI(fileInfo.parMetaAddr, fileInfo.fsObjId, parentAsFile.getPath(), parentAsFile.getName(), trans);
+		}
+	}
     
     /**
      * Add a layout file to the database. 
@@ -306,13 +439,15 @@ long addLayoutFile(long parentObjId,
         int fileType,
         String name, long size) {
         try {
+            
             // The file system may be null for layout files
             Long fsObjIdForDb = fsObjId;
             if (fsObjId == 0) {
                 fsObjIdForDb = null;
             }
             
-            return caseDb.addFileJNI(parentObjId, 
+            beginTransaction();
+            long objId = caseDb.addFileJNI(parentObjId, 
                 fsObjIdForDb, dataSourceObjId,
                 fileType,
                 null, null, name,
@@ -327,9 +462,12 @@ long addLayoutFile(long parentObjId,
                 null, TskData.FileKnown.UNKNOWN,
                 null, null, 
                 true, trans);
+            commitTransaction();
+            return objId;
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding layout file to the database - parent object ID: " + parentObjId
                     + ", file system object ID: " + fsObjId + ", name: " + name, ex);
+            revertTransaction();
             return -1;
         }
     }    
@@ -346,32 +484,39 @@ long addLayoutFile(long parentObjId,
      * @return 0 if successful, -1 if not
      */
     long addLayoutFileRange(long objId, long byteStart, long byteLen, long seq) {
-        try {
-            caseDb.addLayoutFileRangeJNI(objId, byteStart, byteLen, seq, trans);
-            return 0;
-        } catch (TskCoreException ex) {
-            logger.log(Level.SEVERE, "Error adding layout file range to the database - layout file ID: " + objId 
-                + ", byte start: " + byteStart, ex);
-            return -1;
+        batchedLayoutRanges.add(new LayoutRangeInfo(objId, byteStart, byteLen, seq));
+        
+        if (batchedLayoutRanges.size() > BATCH_FILE_THRESHOLD) {
+            return addBatchedLayoutRangesToDb();
         }
+        return 0;
     }
     
     /**
-     * Look up the parent of a file based on metadata address and name/path.
-     * Intended to be called from the native code during the add image process.
-     * 
-     * @param metaAddr
-     * @param fsObjId
-     * @param path
-     * @param name
+     * Add the current set of layout ranges to the database.
      * 
-     * @return The object ID of the parent or -1 if not found
+     * @return 0 if successful, -1 if not
      */
-    long findParentObjId(long metaAddr, long fsObjId, String path, String name) {
+    private long addBatchedLayoutRangesToDb() {
         try {
-            return caseDb.findParentObjIdJNI(metaAddr, fsObjId, path, name, trans);
+            beginTransaction();
+    
+            for (LayoutRangeInfo range : batchedLayoutRanges) {
+                try {
+                    caseDb.addLayoutFileRangeJNI(range.objId, range.byteStart, range.byteLen, range.seq, trans);
+                } catch (TskCoreException ex) {
+                    logger.log(Level.SEVERE, "Error adding layout file range to the database - layout file ID: " + range.objId 
+                        + ", byte start: " + range.byteStart, ex);
+                    revertTransaction();
+                    return -1;
+                }
+            }
+            commitTransaction();
+            batchedLayoutRanges.clear();
+            return 0;
         } catch (TskCoreException ex) {
-            logger.log(Level.WARNING, "Error looking up parent with meta addr: " + metaAddr + " and name " + name, ex);
+            logger.log(Level.SEVERE, "Error adding batched files to database", ex);
+            revertTransaction();
             return -1;
         }
     }
@@ -391,10 +536,163 @@ long addUnallocFsBlockFilesParent(long fsObjId, String name) {
                 logger.log(Level.SEVERE, "Error - root directory for file system ID {0} not found", fsObjId);
                 return -1;
             }
-            return caseDb.addVirtualDirectoryJNI(fsIdToRootDir.get(fsObjId), name, trans);
+            beginTransaction();
+            VirtualDirectory dir = caseDb.addVirtualDirectory(fsIdToRootDir.get(fsObjId), name, trans);
+            commitTransaction();
+            return dir.getId();
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error creating virtual directory " + name + " under file system ID " + fsObjId, ex);
+            revertTransaction();
             return -1;
         }
     }
+    
+    /**
+     * Class to use as a key into the parent object ID map
+     */
+    private class ParentCacheKey {
+        long fsObjId;
+        long metaAddr;
+        long seqNum;
+        String path;
+        
+        /**
+         * Create the key into the parent dir cache.
+         * Only NTFS uses the seqNum of the parent. For all file systems set to zero.
+         * 
+         * @param fsObjId  The file system object ID.
+         * @param metaAddr The metadata address of the directory.
+         * @param seqNum   The sequence number of the directory. Unused unless file system is NTFS.
+         * @param path     The path to the directory (should not include final slash unless root dir).
+         */
+        ParentCacheKey(long fsObjId, long metaAddr, long seqNum, String path) {
+            this.fsObjId = fsObjId;
+            this.metaAddr = metaAddr;
+            if (fsIdToFsType.containsKey(fsObjId) 
+                    && (fsIdToFsType.get(fsObjId).equals(TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS)
+                        || fsIdToFsType.get(fsObjId).equals(TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS_DETECT))) {
+                this.seqNum = seqNum;
+            } else {
+                this.seqNum = 0;
+            }
+            this.path = path;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (! (obj instanceof ParentCacheKey)) {
+                return false;
+            }
+          
+            ParentCacheKey otherKey = (ParentCacheKey) obj;
+            if (this.fsObjId != otherKey.fsObjId 
+                    || this.metaAddr != otherKey.metaAddr
+                    || this.seqNum != otherKey.seqNum) {
+                return false;
+            }
+            
+            return StringUtils.equals(this.path, otherKey.path);
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 3;
+            hash = 31 * hash + (int) (this.fsObjId ^ (this.fsObjId >>> 32));
+            hash = 31 * hash + (int) (this.metaAddr ^ (this.metaAddr >>> 32));
+            hash = 31 * hash + (int) (this.seqNum ^ (this.seqNum >>> 32));
+            hash = 31 * hash + Objects.hashCode(this.path);
+            return hash;
+        }
+    }
+    
+    /**
+     * Utility class to hold data for layout ranges waiting
+     * to be added to the database.
+     */
+    private class LayoutRangeInfo {
+        long objId;
+        long byteStart;
+        long byteLen;
+        long seq;
+        
+        LayoutRangeInfo(long objId, long byteStart, long byteLen, long seq) {
+            this.objId = objId;
+            this.byteStart = byteStart;
+            this.byteLen = byteLen;
+            this.seq = seq;
+        }
+    }
+    
+    /**
+     * Utility class to hold data for files waiting to be
+     * added to the database.
+     */
+    private class FileInfo {
+        long parentObjId; 
+        long fsObjId;
+        long dataSourceObjId;
+        int fsType;
+        int attrType;
+        int attrId;
+        String name;
+        long metaAddr; 
+        long metaSeq;
+        int dirType;
+        int metaType;
+        int dirFlags;
+        int metaFlags;
+        long size;
+        long crtime;
+        long ctime;
+        long atime;
+        long mtime;
+        int meta_mode;
+        int gid;
+        int uid;
+        String escaped_path;
+        String extension;
+        long seq;
+        long parMetaAddr;
+        long parSeq;
+        
+        FileInfo(long parentObjId, 
+            long fsObjId, long dataSourceObjId,
+            int fsType,
+            int attrType, int attrId, String name,
+            long metaAddr, long metaSeq,
+            int dirType, int metaType, int dirFlags, int metaFlags,
+            long size,
+            long crtime, long ctime, long atime, long mtime,
+            int meta_mode, int gid, int uid,
+            String escaped_path, String extension, 
+            long seq, long parMetaAddr, long parSeq) {
+            
+            this.parentObjId = parentObjId;
+            this.fsObjId = fsObjId;
+            this.dataSourceObjId = dataSourceObjId;
+            this.fsType = fsType;
+            this.attrType = attrType;
+            this.attrId = attrId;
+            this.name = name;
+            this.metaAddr = metaAddr; 
+            this.metaSeq = metaSeq;
+            this.dirType = dirType;
+            this.metaType = metaType;
+            this.dirFlags = dirFlags;
+            this.metaFlags = metaFlags;
+            this.size = size;
+            this.crtime = crtime;
+            this.ctime = ctime;
+            this.atime = atime;
+            this.mtime = mtime;
+            this.meta_mode = meta_mode;
+            this.gid = gid;
+            this.uid = uid;
+            this.escaped_path = escaped_path;
+            this.extension = extension;
+            this.seq = seq;
+            this.parMetaAddr = parMetaAddr;
+            this.parSeq = parSeq;
+        }
+    }
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 8c898af384c9787a68a9e1d2542344cf45beb8ce..bb9dd9e8334fc5c045e84611ed28526bc0035696 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -2120,7 +2120,7 @@ private CaseDbSchemaVersionNumber updateFromSchema8dot4toSchema8dot5(CaseDbSchem
 
 			statement.execute("ALTER TABLE tag_names ADD COLUMN rank INTEGER");
 
-			String insertStmt = "INSERT INTO tsk_tag_sets (name) VALUES ('Project VIC (United States)')";
+			String insertStmt = "INSERT INTO tsk_tag_sets (name) VALUES ('Project VIC')";
 			if (getDatabaseType() == DbType.POSTGRESQL) {
 				statement.execute(insertStmt, Statement.RETURN_GENERATED_KEYS);
 			} else {
@@ -2130,17 +2130,48 @@ private CaseDbSchemaVersionNumber updateFromSchema8dot4toSchema8dot5(CaseDbSchem
 				if (resultSet != null && resultSet.next()) {
 					int tagSetId = resultSet.getInt(1);
 
-					String updateQuery = "UPDATE tag_names SET tag_set_id = %d, color = '%s', rank = %d WHERE display_name = '%s'";
-					statement.executeUpdate(String.format(updateQuery, tagSetId, "Red", 1, "CAT-1: Child Exploitation (Illegal)"));
-					statement.executeUpdate(String.format(updateQuery, tagSetId, "Lime", 2, "CAT-2: Child Exploitation (Non-Illegal/Age Difficult)"));
-					statement.executeUpdate(String.format(updateQuery, tagSetId, "Yellow", 3, "CAT-3: CGI/Animation (Child Exploitive)"));
-					statement.executeUpdate(String.format(updateQuery, tagSetId, "Purple", 4, "CAT-4: Exemplar/Comparison (Internal Use Only)"));
-					statement.executeUpdate(String.format(updateQuery, tagSetId, "Green", 5, "CAT-5: Non-pertinent"));
-					statement.executeUpdate(String.format(updateQuery, tagSetId, "Silver", 0, "CAT-0: Uncategorized", 0));
+					String updateQuery = "UPDATE tag_names SET tag_set_id = %d, color = '%s', rank = %d, display_name = '%s' WHERE display_name = '%s'";
+					statement.executeUpdate(String.format(updateQuery, tagSetId, "Red", 1, "Child Exploitation (Illegal)", "CAT-1: Child Exploitation (Illegal)"));
+					statement.executeUpdate(String.format(updateQuery, tagSetId, "Lime", 2, "Child Exploitation (Non-Illegal/Age Difficult)", "CAT-2: Child Exploitation (Non-Illegal/Age Difficult)"));
+					statement.executeUpdate(String.format(updateQuery, tagSetId, "Yellow", 3, "CGI/Animation (Child Exploitive)", "CAT-3: CGI/Animation (Child Exploitive)"));
+					statement.executeUpdate(String.format(updateQuery, tagSetId, "Purple", 4, "Exemplar/Comparison (Internal Use Only)", "CAT-4: Exemplar/Comparison (Internal Use Only)"));
+					statement.executeUpdate(String.format(updateQuery, tagSetId, "Fuchsia", 5, "Non-pertinent", "CAT-5: Non-pertinent"));
+
+					String deleteContentTag = "DELETE FROM content_tags WHERE tag_name_id IN (SELECT tag_name_id from tag_names WHERE display_name LIKE 'CAT-0: Uncategorized')";
+					String deleteArtifactTag = "DELETE FROM blackboard_artifact_tags WHERE tag_name_id IN (SELECT tag_name_id from tag_names WHERE display_name LIKE 'CAT-0: Uncategorized')";
+					String deleteCat0 = "DELETE FROM tag_names WHERE display_name = 'CAT-0: Uncategorized'";
+					statement.executeUpdate(deleteContentTag);
+					statement.executeUpdate(deleteArtifactTag);
+					statement.executeUpdate(deleteCat0);
+
 				} else {
 					throw new TskCoreException("Failed to retrieve the default tag_set_id from DB");
 				}
 			}
+			
+			// Add data_source_obj_id column to the tsk_files table. For newly created cases
+			// this column will have a foreign key constraint on the data_source_info table.
+			// There does not seem to be a reasonable way to do this in an upgrade,
+			// so upgraded cases will be missing the foreign key.
+			switch (getDatabaseType()) {
+				case POSTGRESQL:
+					statement.execute("ALTER TABLE tsk_fs_info ADD COLUMN data_source_obj_id BIGINT NOT NULL DEFAULT -1;");
+					break;
+				case SQLITE:
+					statement.execute("ALTER TABLE tsk_fs_info ADD COLUMN data_source_obj_id INTEGER NOT NULL DEFAULT -1;");
+					break;
+			}
+			Statement updateStatement = connection.createStatement();
+			try (ResultSet resultSet = statement.executeQuery("SELECT obj_id FROM tsk_fs_info")) {
+				while (resultSet.next()) {
+					long fsId = resultSet.getLong("obj_id");
+					long dataSourceId = getDataSourceObjectId(connection, fsId);
+					updateStatement.executeUpdate("UPDATE tsk_fs_info SET data_source_obj_id = " + dataSourceId + " WHERE obj_id = " + fsId + ";");
+				}
+			} finally {
+				closeStatement(updateStatement);
+			}
+			
 			return new CaseDbSchemaVersionNumber(8, 5);
 
 		} finally {
@@ -6065,19 +6096,23 @@ public FileSystem addFileSystem(long parentObjId, long imgOffset, TskData.TSK_FS
 			CaseDbConnection connection = transaction.getConnection();
 			long newObjId = addObject(parentObjId, TskData.ObjectType.FS.getObjectType(), connection);
 
+			// Get the data source object ID
+			long dataSourceId = getDataSourceObjectId(connection, newObjId);
+			
 			// Add a row to tsk_fs_info
-			// INSERT INTO tsk_fs_info (obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)
+			// INSERT INTO tsk_fs_info (obj_id, data_source_obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)
 			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FS_INFO);
 			preparedStatement.clearParameters();
 			preparedStatement.setLong(1, newObjId);
-			preparedStatement.setLong(2, imgOffset);
-			preparedStatement.setInt(3, type.getValue());
-			preparedStatement.setLong(4, blockSize);
-			preparedStatement.setLong(5, blockCount);
-			preparedStatement.setLong(6, rootInum);
-			preparedStatement.setLong(7, firstInum);
-			preparedStatement.setLong(8, lastInum);
-			preparedStatement.setString(9, displayName);
+			preparedStatement.setLong(2, dataSourceId);
+			preparedStatement.setLong(3, imgOffset);
+			preparedStatement.setInt(4, type.getValue());
+			preparedStatement.setLong(5, blockSize);
+			preparedStatement.setLong(6, blockCount);
+			preparedStatement.setLong(7, rootInum);
+			preparedStatement.setLong(8, firstInum);
+			preparedStatement.setLong(9, lastInum);
+			preparedStatement.setString(10, displayName);
 			connection.executeUpdate(preparedStatement);
 
 			// Create the new FileSystem object
@@ -7871,70 +7906,30 @@ Directory getDirectoryById(long id, FileSystem parentFs) throws TskCoreException
 	 * @param image Image to lookup FileSystem for
 	 *
 	 * @return Collection of FileSystems in the image
+	 * 
+	 * @throws TskCoreException
 	 */
-	public Collection<FileSystem> getFileSystems(Image image) {
-		List<FileSystem> fileSystems = new ArrayList<FileSystem>();
-		CaseDbConnection connection;
-		try {
-			connection = connections.getConnection();
-		} catch (TskCoreException ex) {
-			logger.log(Level.SEVERE, "Error getting file systems for image " + image.getId(), ex); //NON-NLS
-			return fileSystems;
-		}
+	public Collection<FileSystem> getImageFileSystems(Image image) throws TskCoreException {		
+		List<FileSystem> fileSystems = new ArrayList<>();
+		CaseDbConnection connection = connections.getConnection();
+		
 		acquireSingleUserCaseReadLock();
 		Statement s = null;
 		ResultSet rs = null;
+		String queryStr = "SELECT * FROM tsk_fs_info WHERE data_source_obj_id = " + image.getId();
 		try {
 			s = connection.createStatement();
-
-			// Get all the file systems.
-			List<FileSystem> allFileSystems = new ArrayList<FileSystem>();
-			try {
-				rs = connection.executeQuery(s, "SELECT * FROM tsk_fs_info"); //NON-NLS
-				while (rs.next()) {
-					TskData.TSK_FS_TYPE_ENUM fsType = TskData.TSK_FS_TYPE_ENUM.valueOf(rs.getInt("fs_type")); //NON-NLS
-					FileSystem fs = new FileSystem(this, rs.getLong("obj_id"), "", rs.getLong("img_offset"), //NON-NLS
-							fsType, rs.getLong("block_size"), rs.getLong("block_count"), //NON-NLS
-							rs.getLong("root_inum"), rs.getLong("first_inum"), rs.getLong("last_inum")); //NON-NLS
-					fs.setParent(null);
-					allFileSystems.add(fs);
-				}
-			} catch (SQLException ex) {
-				logger.log(Level.SEVERE, "There was a problem while trying to obtain all file systems", ex); //NON-NLS
-			} finally {
-				closeResultSet(rs);
-				rs = null;
-			}
-
-			// For each file system, find the image to which it belongs by iteratively
-			// climbing the tsk_ojbects hierarchy only taking those file systems
-			// that belong to this image.
-			for (FileSystem fs : allFileSystems) {
-				Long imageID = null;
-				Long currentObjID = fs.getId();
-				while (imageID == null) {
-					try {
-						rs = connection.executeQuery(s, "SELECT * FROM tsk_objects WHERE tsk_objects.obj_id = " + currentObjID); //NON-NLS
-						rs.next();
-						currentObjID = rs.getLong("par_obj_id"); //NON-NLS
-						if (rs.getInt("type") == TskData.ObjectType.IMG.getObjectType()) { //NON-NLS
-							imageID = rs.getLong("obj_id"); //NON-NLS
-						}
-					} catch (SQLException ex) {
-						logger.log(Level.SEVERE, "There was a problem while trying to obtain this image's file systems", ex); //NON-NLS
-					} finally {
-						closeResultSet(rs);
-						rs = null;
-					}
-				}
-
-				// see if imageID is this image's ID
-				if (imageID == image.getId()) {
-					fileSystems.add(fs);
-				}
+			rs = connection.executeQuery(s, queryStr); //NON-NLS
+			while (rs.next()) {
+				TskData.TSK_FS_TYPE_ENUM fsType = TskData.TSK_FS_TYPE_ENUM.valueOf(rs.getInt("fs_type")); //NON-NLS
+				FileSystem fs = new FileSystem(this, rs.getLong("obj_id"), "", rs.getLong("img_offset"), //NON-NLS
+						fsType, rs.getLong("block_size"), rs.getLong("block_count"), //NON-NLS
+						rs.getLong("root_inum"), rs.getLong("first_inum"), rs.getLong("last_inum")); //NON-NLS
+				fs.setParent(null);
+				fileSystems.add(fs);
 			}
 		} catch (SQLException ex) {
-			logger.log(Level.SEVERE, "Error getting case database connection", ex); //NON-NLS
+			throw new TskCoreException("Error looking up files systems. Query: " + queryStr, ex); //NON-NLS
 		} finally {
 			closeResultSet(rs);
 			closeStatement(s);
@@ -11115,10 +11110,10 @@ long findParentObjIdJNI(long metaAddr, long fsObjId, String path, String name, C
 			if (resultSet.next()) {
 				return resultSet.getLong("obj_id");
 			} else {
-				throw new TskCoreException(String.format("Error looking up parent meta addr %d", metaAddr));
+				throw new TskCoreException(String.format("Error looking up parent - meta addr: %d, path: %s, name: %s", metaAddr, path, name));
 			}
 		} catch (SQLException ex) {
-			throw new TskCoreException(String.format("Error looking up parent meta addr %d", metaAddr), ex);
+			throw new TskCoreException(String.format("Error looking up parent - meta addr: %d, path: %s, name: %s", metaAddr, path, name), ex);
 		} finally {
 			closeResultSet(resultSet);
 		}
@@ -11328,122 +11323,7 @@ void addLayoutFileRangeJNI(long objId, long byteStart, long byteLen,
 			releaseSingleUserCaseWriteLock();
 		}
 	}
-
-	/**
-	 * Adds a virtual directory to the database and returns a VirtualDirectory
-	 * object representing it. For use with the JNI callbacks associated with
-	 * the add image process.
-	 *
-	 * @param parentId      the ID of the parent, or 0 if NULL
-	 * @param directoryName the name of the virtual directory to create
-	 * @param transaction   the transaction in the scope of which the operation
-	 *                      is to be performed, managed by the caller
-	 *
-	 * @return The object ID of the new virtual directory
-	 *
-	 * @throws TskCoreException
-	 */
-	long addVirtualDirectoryJNI(long parentId, String directoryName, CaseDbTransaction transaction) throws TskCoreException {
-		acquireSingleUserCaseWriteLock();
-		ResultSet resultSet = null;
-		try {
-			// Get the parent path.
-			CaseDbConnection connection = transaction.getConnection();
-
-			String parentPath;
-			Content parent = this.getAbstractFileById(parentId, connection);
-			if (parent instanceof AbstractFile) {
-				if (isRootDirectory((AbstractFile) parent, transaction)) {
-					parentPath = "/";
-				} else {
-					parentPath = ((AbstractFile) parent).getParentPath() + parent.getName() + "/"; //NON-NLS
-				}
-			} else {
-				// The parent was either null or not an abstract file
-				parentPath = "/";
-			}
-
-			// Insert a row for the virtual directory into the tsk_objects table.
-			long newObjId = addObject(parentId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
-
-			// Insert a row for the virtual directory into the tsk_files table.
-			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type,
-			// dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id,extension)
-			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
-			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
-			statement.clearParameters();
-			statement.setLong(1, newObjId);
-
-			// If the parent is part of a file system, grab its file system ID
-			if (0 != parentId) {
-				long parentFs = this.getFileSystemId(parentId, connection);
-				if (parentFs != -1) {
-					statement.setLong(2, parentFs);
-				} else {
-					statement.setNull(2, java.sql.Types.BIGINT);
-				}
-			} else {
-				statement.setNull(2, java.sql.Types.BIGINT);
-			}
-
-			// name
-			statement.setString(3, directoryName);
-
-			//type
-			statement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType());
-			statement.setShort(5, (short) 1);
-
-			//flags
-			final TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
-			statement.setShort(6, dirType.getValue());
-			final TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
-			statement.setShort(7, metaType.getValue());
-
-			//allocated
-			final TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
-			statement.setShort(8, dirFlag.getValue());
-			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
-					| TSK_FS_META_FLAG_ENUM.USED.getValue());
-			statement.setShort(9, metaFlags);
-
-			//size
-			statement.setLong(10, 0);
-
-			//  nulls for params 11-14
-			statement.setNull(11, java.sql.Types.BIGINT);
-			statement.setNull(12, java.sql.Types.BIGINT);
-			statement.setNull(13, java.sql.Types.BIGINT);
-			statement.setNull(14, java.sql.Types.BIGINT);
-
-			statement.setNull(15, java.sql.Types.VARCHAR); // MD5
-			statement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
-			statement.setNull(17, java.sql.Types.VARCHAR); // MIME type	
-
-			// parent path
-			statement.setString(18, parentPath);
-
-			// data source object id (same as object id if this is a data source)
-			long dataSourceObjectId;
-			if (0 == parentId) {
-				dataSourceObjectId = newObjId;
-			} else {
-				dataSourceObjectId = getDataSourceObjectId(connection, parentId);
-			}
-			statement.setLong(19, dataSourceObjectId);
-
-			//extension, since this is not really file we just set it to null
-			statement.setString(20, null);
-			connection.executeUpdate(statement);
-
-			return newObjId;
-		} catch (SQLException e) {
-			throw new TskCoreException("Error creating virtual directory '" + directoryName + "'", e);
-		} finally {
-			closeResultSet(resultSet);
-			releaseSingleUserCaseWriteLock();
-		}
-	}
-
+	
 	/**
 	 * Stores a pair of object ID and its type
 	 */
@@ -11660,8 +11540,8 @@ private enum PREPARED_STATEMENT {
 		INSERT_VS_PART_SQLITE("INSERT INTO tsk_vs_parts (obj_id, addr, start, length, desc, flags) VALUES (?, ?, ?, ?, ?, ?)"),
 		INSERT_VS_PART_POSTGRESQL("INSERT INTO tsk_vs_parts (obj_id, addr, start, length, descr, flags) VALUES (?, ?, ?, ?, ?, ?)"),
 		INSERT_POOL_INFO("INSERT INTO tsk_pool_info (obj_id, pool_type) VALUES (?, ?)"),
-		INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)"
-				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
+		INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, data_source_obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)"
+				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
 		SELECT_OBJ_ID_BY_META_ADDR_AND_PATH("SELECT obj_id FROM tsk_files WHERE meta_addr = ? AND fs_obj_id = ? AND parent_path = ? AND name = ?"),
 		SELECT_TAG_NAME_BY_ID("SELECT * FROM tag_names where tag_name_id = ?");
 
@@ -13015,6 +12895,25 @@ public LocalFile addLocalFile(String fileName, String localPath,
 	public AddImageProcess makeAddImageProcess(String timezone, boolean addUnallocSpace, boolean noFatFsOrphans) {
 		return this.caseHandle.initAddImageProcess(timezone, addUnallocSpace, noFatFsOrphans, "", this);
 	}
+	
+	/**
+	 * Helper to return FileSystems in an Image
+	 *
+	 * @param image Image to lookup FileSystem for
+	 *
+	 * @return Collection of FileSystems in the image
+	 * 
+	 * @deprecated Use getImageFileSystems which throws an exception if an error occurs.
+	 */
+	@Deprecated
+	public Collection<FileSystem> getFileSystems(Image image) {
+		try {
+			return getImageFileSystems(image);
+		} catch (TskCoreException ex) {
+			logger.log(Level.SEVERE, "Error loading all file systems for image with ID {0}", image.getId());
+			return new ArrayList<>();
+		}
+	}
 
 	/**
 	 * Acquires a write lock, but only if this is a single-user case. Always
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
index eaf0cba9dcbaca06b0049cd4361fc3f673ce3221..4fe44e69095ea9e9a78621ed570f2a5b3417896d 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -426,15 +426,13 @@ void free() throws TskCoreException {
 		long addImageInfo(long deviceObjId, List<String> imageFilePaths, String timeZone, SleuthkitCase skCase) throws TskCoreException {
 			JniDbHelper dbHelper = new JniDbHelper(skCase);
 			try {
-				dbHelper.beginTransaction();
 				long tskAutoDbPointer = initializeAddImgNat(dbHelper, timezoneLongToShort(timeZone), false, false, false);
 				runOpenAndAddImgNat(tskAutoDbPointer, UUID.randomUUID().toString(), imageFilePaths.toArray(new String[0]), imageFilePaths.size(), timeZone);				
 				long id = finishAddImgNat(tskAutoDbPointer);
-				dbHelper.commitTransaction();
+				dbHelper.finish();
 				skCase.addDataSourceToHasChildrenMap();
 				return id;
 			} catch (TskDataException ex) {
-				dbHelper.revertTransaction();
 				throw new TskCoreException("Error adding image to case database", ex);
 			}
 		}
@@ -526,7 +524,6 @@ public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws
 						}
 						if (!isCanceled) { //with isCanceled being guarded by this it will have the same value everywhere in this synchronized block
 							imageHandle = openImage(imageFilePaths, sectorSize, false, caseDbIdentifier);
-							dbHelper.beginTransaction();
 							tskAutoDbPointer = initAddImgNat(dbHelper, timezoneLongToShort(timeZone), addUnallocSpace, skipFatFsOrphans);
 						}
 						if (0 == tskAutoDbPointer) {
@@ -576,8 +573,6 @@ public synchronized void revert() throws TskCoreException {
 						throw new TskCoreException("AddImgProcess::revert: AutoDB pointer is NULL");
 					}
 					
-					dbHelper.revertTransaction();
-					
 					// Delete the object in the native code
 					finishAddImgNat(tskAutoDbPointer);
 					tskAutoDbPointer = 0;
@@ -602,7 +597,7 @@ public synchronized long commit() throws TskCoreException {
 						throw new TskCoreException("AddImgProcess::commit: AutoDB pointer is NULL");
 					}
 
-					dbHelper.commitTransaction();
+					dbHelper.finish();
 
 					// Get the image ID and delete the object in the native code
 					long id = finishAddImgNat(tskAutoDbPointer);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java b/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
index 10e002d5d0cd993d238abb40465efe373011287b..284a86cde5a985048eccf073a8ad0662380b597b 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
@@ -136,11 +136,12 @@ public TagSet addTagSet(String name, List<TagName> tagNames) throws TskCoreExcep
 	}
 
 	/**
-	 * Remove a row from the tag set table. The TagNames in the TagSet will not
-	 * be deleted, nor will any tags with the TagNames from the deleted tag set
-	 * be deleted.
+	 * Remove a row from the tag set table. If the given TagSet has a valid list
+	 * of TagNames the TagNames will be removed from the tag_name table if there
+	 * are not references to the TagNames in the content_tag or
+	 * blackboard_artifact_tag table.
 	 *
-	 * @param tagSet TagSet to be deleted
+	 * @param tagSet TagSet to be deleted.
 	 *
 	 * @throws TskCoreException
 	 */
@@ -149,19 +150,26 @@ public void deleteTagSet(TagSet tagSet) throws TskCoreException {
 			throw new IllegalArgumentException("Error adding deleting TagSet, TagSet object was null");
 		}
 
-		CaseDbConnection connection = skCase.getConnection();
-		skCase.acquireSingleUserCaseWriteLock();
-		try (Statement stmt = connection.createStatement()) {
-			connection.beginTransaction();
-			String queryTemplate = "DELETE FROM tsk_tag_sets WHERE tag_set_id = '%d'";
-			stmt.execute(String.format(queryTemplate, tagSet.getId()));
-			connection.commitTransaction();
-		} catch (SQLException ex) {
-			connection.rollbackTransaction();
-			throw new TskCoreException(String.format("Error deleting tag set where id = %d.", tagSet.getId()), ex);
-		} finally {
-			connection.close();
-			skCase.releaseSingleUserCaseWriteLock();
+		if (isTagSetInUse(tagSet)) {
+			throw new TskCoreException("Unable to delete TagSet (%d). TagSet TagName list contains TagNames that are currently in use.");
+		}
+
+		try (CaseDbConnection connection = skCase.getConnection()) {
+			skCase.acquireSingleUserCaseWriteLock();
+			try (Statement stmt = connection.createStatement()) {
+				connection.beginTransaction();
+				String queryTemplate = "DELETE FROM tag_names WHERE tag_name_id IN (SELECT tag_name_id FROM tag_names WHERE tag_set_id = %d)";
+				stmt.execute(String.format(queryTemplate, tagSet.getId()));
+
+				queryTemplate = "DELETE FROM tsk_tag_sets WHERE tag_set_id = '%d'";
+				stmt.execute(String.format(queryTemplate, tagSet.getId()));
+				connection.commitTransaction();
+			} catch (SQLException ex) {
+				connection.rollbackTransaction();
+				throw new TskCoreException(String.format("Error deleting tag set where id = %d.", tagSet.getId()), ex);
+			} finally {
+				skCase.releaseSingleUserCaseWriteLock();
+			}
 		}
 	}
 
@@ -414,6 +422,48 @@ public ContentTagChange addContentTag(Content content, TagName tagName, String c
 		}
 	}
 
+	/**
+	 * Determine if the given TagSet contains TagNames that are currently in
+	 * use, ie there is an existing ContentTag or ArtifactTag that uses TagName.
+	 *
+	 * @param tagSet The Tagset to check.
+	 *
+	 * @return Return true if the TagSet is in use.
+	 *
+	 * @throws TskCoreException
+	 */
+	private boolean isTagSetInUse(TagSet tagSet) throws TskCoreException {
+		try (CaseDbConnection connection = skCase.getConnection()) {
+			List<TagName> tagNameList = tagSet.getTagNames();
+			if (tagNameList != null && !tagNameList.isEmpty()) {
+				skCase.acquireSingleUserCaseReadLock();
+				try {
+					String statement = String.format("SELECT tag_id FROM content_tags WHERE tag_name_id IN (SELECT tag_name_id FROM tag_names WHERE tag_set_id = %d)", tagSet.getId());
+					try (Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(statement)) {
+						if (resultSet.next()) {
+							return true;
+						}
+					} catch (SQLException ex) {
+						throw new TskCoreException(String.format("Failed to determine if TagSet is in use (%s)", tagSet.getId()), ex);
+					}
+
+					statement = String.format("SELECT tag_id FROM blackboard_artifact_tags WHERE tag_name_id IN (SELECT tag_name_id FROM tag_names WHERE tag_set_id = %d)", tagSet.getId());
+					try (Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery(statement)) {
+						if (resultSet.next()) {
+							return true;
+						}
+					} catch (SQLException ex) {
+						throw new TskCoreException(String.format("Failed to determine if TagSet is in use (%s)", tagSet.getId()), ex);
+					}
+				} finally {
+					skCase.releaseSingleUserCaseReadLock();
+				}
+			}
+		}
+
+		return false;
+	}
+
 	/**
 	 * Returns a list of all of the TagNames that are apart of the given TagSet.
 	 *
diff --git a/tools/autotools/tsk_gettimes.cpp b/tools/autotools/tsk_gettimes.cpp
index ce411a00a8053502ecf307a7d1c68e1d8c253715..3686ecad541b14af9ab02dffe7fd612364051542 100644
--- a/tools/autotools/tsk_gettimes.cpp
+++ b/tools/autotools/tsk_gettimes.cpp
@@ -46,6 +46,7 @@ class TskGetTimes:public TskAuto {
 	TskGetTimes(int32_t, bool);
     virtual TSK_RETVAL_ENUM processFile(TSK_FS_FILE * fs_file, const char *path);
     virtual TSK_FILTER_ENUM filterVol(const TSK_VS_PART_INFO * vs_part);
+    virtual TSK_FILTER_ENUM filterPool(const TSK_POOL_INFO * pool_info);
     virtual TSK_FILTER_ENUM filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol);
     virtual TSK_FILTER_ENUM filterFs(TSK_FS_INFO * fs_info);
     virtual uint8_t handleError();
@@ -125,6 +126,14 @@ TskGetTimes::filterVol(const TSK_VS_PART_INFO * vs_part)
     return TSK_FILTER_CONT;
 }
 
+TSK_FILTER_ENUM
+TskGetTimes::filterPool(const TSK_POOL_INFO * pool_info)
+{
+    // There's nothing to do, but we need to override this to allow the pool
+    // to be processed.
+    return TSK_FILTER_CONT;
+}
+
 
 TSK_FILTER_ENUM
 TskGetTimes::filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol)
diff --git a/tsk/auto/auto.cpp b/tsk/auto/auto.cpp
index e20b1dac8b4520117cb88a7ab7c784c684fa07f2..2114f0b243830954d67e6e27ca030ad51cf1cd9d 100755
--- a/tsk/auto/auto.cpp
+++ b/tsk/auto/auto.cpp
@@ -220,7 +220,7 @@ TSK_FILTER_ENUM
 TskAuto::filterPool(const TSK_POOL_INFO * /*pool_info*/) {
     /* Most of our tools can't handle pool volumes yet */
     if (tsk_verbose)
-        fprintf(stderr, "filterPoolVol: Pool handling is not yet implemented for this tool\n");
+        fprintf(stderr, "filterPool: Pool handling is not yet implemented for this tool\n");
     return TSK_FILTER_SKIP;
 }
 
diff --git a/tsk/base/tsk_base.h b/tsk/base/tsk_base.h
index 91daa425392cd9526b9d9ca770d69b908d42c267..310ec6cc98e049b302861052c37cca7d6832ae59 100644
--- a/tsk/base/tsk_base.h
+++ b/tsk/base/tsk_base.h
@@ -56,6 +56,10 @@
 // get some other TSK / OS settings
 #include "tsk_os.h"
 
+#ifdef TSK_WIN32
+#define strncasecmp _strnicmp
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/tsk/fs/fs_dir.c b/tsk/fs/fs_dir.c
index 5abd9b8c140350ac93cb59ae5cf739fd8e9fa7a8..199f56aaa530a81c135e6d9f6f6ca3e569d2393d 100644
--- a/tsk/fs/fs_dir.c
+++ b/tsk/fs/fs_dir.c
@@ -17,7 +17,6 @@
 #include "tsk_fs_i.h"
 #include "tsk_fatfs.h"
 
-
 /** \internal
 * Allocate a FS_DIR structure to load names into.
 *
@@ -541,6 +540,74 @@ save_inum_named(TSK_FS_INFO *a_fs, DENT_DINFO *dinfo) {
     tsk_release_lock(&a_fs->list_inum_named_lock);
 }
 
+/**
+ * Prioritize folders in the root directory based on which are expected to contain user content.
+ */
+static TSK_RETVAL_ENUM 
+prioritizeDirNames(TSK_FS_NAME * names, size_t count, int * indexToOrderedIndex) {
+    const int HIGH = 0;
+    const int MED = 1;
+    const int LOW = 2;
+    const int LAST = 3;
+    int * scores;
+    int i, currentScore;
+
+    scores = (int *)tsk_malloc(count * sizeof(int));
+    if (scores == NULL) {
+        return TSK_ERR;
+    }
+
+    // Default level is medium for any files/folders that do not match one of the patterns below.
+    // This includes the Program Files and Applications folders.
+    for (i = 0; i < count; i++) {
+        scores[i] = MED;
+    }
+
+    // Get the score for each name. Currnetly all patterns match the beginning of the name.
+    for (i = 0; i < count; i++) {
+        TSK_FS_NAME* name = &(names[i]);
+        if (name->name != NULL) {
+            if (0 == strncasecmp(name->name, "Users", strlen("Users"))) {
+                scores[i] = HIGH;
+            }
+            else if (0 == strncasecmp(name->name, "Documents and Settings", strlen("Documents and Settings"))) {
+                scores[i] = HIGH;
+            }
+            else if (0 == strncasecmp(name->name, "home", strlen("home"))) {
+                scores[i] = HIGH;
+            }
+            else if (0 == strncasecmp(name->name, "ProgramData", strlen("ProgramData"))) {
+                scores[i] = HIGH;
+            }
+            else if (0 == strncasecmp(name->name, "Windows", strlen("Windows"))) {
+                scores[i] = LOW;
+            }
+            else if (0 == strncasecmp(name->name, "$Orphan", strlen("$Orphan"))) {
+                scores[i] = LOW;
+            }
+            else if (0 == strncasecmp(name->name, "pagefile", strlen("pagefile"))) {
+                scores[i] = LAST;
+            }
+            else if (0 == strncasecmp(name->name, "hiberfil", strlen("hiberfil"))) {
+                scores[i] = LAST;
+            }
+        }
+    }
+
+    // Order the name entries based on the scores
+    int orderedIndex = 0;
+    for (currentScore = HIGH; currentScore <= LAST; currentScore++) {
+        for (i = 0; i < count; i++) {
+            if (scores[i] == currentScore) {
+                indexToOrderedIndex[orderedIndex] = i;
+                orderedIndex++;
+            }
+        }
+    }
+    free(scores);
+    return TSK_OK;
+}
+
 /* dir_walk local function that is used for recursive calls.  Callers
  * should initially call the non-local version. */
 static TSK_WALK_RET_ENUM
@@ -551,17 +618,34 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
     TSK_FS_DIR *fs_dir;
     TSK_FS_FILE *fs_file;
     size_t i;
+    int* indexToOrderedIndex = NULL;
 
     // get the list of entries in the directory
     if ((fs_dir = tsk_fs_dir_open_meta(a_fs, a_addr)) == NULL) {
         return TSK_WALK_ERROR;
     }
 
+    // If we're in the root folder, sort the files/folders to prioritize user content
+    if (a_addr == a_fs->root_inum) {
+        indexToOrderedIndex = (int *)tsk_malloc(fs_dir->names_used * sizeof(int));
+        if (indexToOrderedIndex == NULL) {
+            tsk_fs_dir_close(fs_dir);
+            return TSK_WALK_ERROR;
+        }
+        if (TSK_OK != prioritizeDirNames(fs_dir->names, fs_dir->names_used, indexToOrderedIndex)) {
+            tsk_fs_dir_close(fs_dir);
+            return TSK_WALK_ERROR;
+        }
+    }
+
     /* Allocate a file structure for the callbacks.  We
      * will allocate fs_meta structures as needed and
      * point into the fs_dir structure for the names. */
     if ((fs_file = tsk_fs_file_alloc(a_fs)) == NULL) {
         tsk_fs_dir_close(fs_dir);
+        if (indexToOrderedIndex != NULL) {
+            free(indexToOrderedIndex);
+        }
         return TSK_WALK_ERROR;
     }
 
@@ -570,7 +654,13 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
 
         /* Point name to the buffer of names.  We need to be
          * careful about resetting this before we free fs_file */
-        fs_file->name = (TSK_FS_NAME *) & fs_dir->names[i];
+        if (indexToOrderedIndex != NULL) {
+            // If we have a priortized list, use it
+            fs_file->name = (TSK_FS_NAME *)& fs_dir->names[indexToOrderedIndex[i]];
+        }
+        else {
+            fs_file->name = (TSK_FS_NAME *)& fs_dir->names[i];
+        }
 
         /* load the fs_meta structure if possible.
          * Must have non-zero inode addr or have allocated name (if inode is 0) */
@@ -596,6 +686,10 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                 fs_file->name = NULL;
                 tsk_fs_file_close(fs_file);
 
+                if (indexToOrderedIndex != NULL) {
+                    free(indexToOrderedIndex);
+                }
+
                 /* free the list -- fs_dir_walk has no way
                  * of knowing that we stopped early w/out error.
                  */
@@ -610,6 +704,9 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                 tsk_fs_dir_close(fs_dir);
                 fs_file->name = NULL;
                 tsk_fs_file_close(fs_file);
+                if (indexToOrderedIndex != NULL) {
+                    free(indexToOrderedIndex);
+                }
                 return TSK_WALK_ERROR;
             }
         }
@@ -674,6 +771,9 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                     tsk_fs_dir_close(fs_dir);
                     fs_file->name = NULL;
                     tsk_fs_file_close(fs_file);
+                    if (indexToOrderedIndex != NULL) {
+                        free(indexToOrderedIndex);
+                    }
                     return TSK_WALK_ERROR;
                 }
 
@@ -688,6 +788,10 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                             "tsk_fs_dir_walk_lcl: directory : %"
                             PRIuINUM " exceeded max length / depth\n", fs_file->name->meta_addr);
                     }
+
+                    if (indexToOrderedIndex != NULL) {
+                        free(indexToOrderedIndex);
+                    }
                     return TSK_WALK_ERROR;
                 }
 
@@ -728,6 +832,10 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
                     tsk_fs_dir_close(fs_dir);
                     fs_file->name = NULL;
                     tsk_fs_file_close(fs_file);
+
+                    if (indexToOrderedIndex != NULL) {
+                        free(indexToOrderedIndex);
+                    }
                     return TSK_WALK_STOP;
                 }
 
@@ -763,6 +871,10 @@ tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo,
     tsk_fs_dir_close(fs_dir);
     fs_file->name = NULL;
     tsk_fs_file_close(fs_file);
+
+    if (indexToOrderedIndex != NULL) {
+        free(indexToOrderedIndex);
+    }
     return TSK_WALK_CONT;
 }
 
diff --git a/win32/BUILDING.txt b/win32/BUILDING.txt
index ca0aa844fce21b4188ffbef1279ba16a3a3ae87a..a39f9ab6b77195a3411404aa312940d27c52ec79 100755
--- a/win32/BUILDING.txt
+++ b/win32/BUILDING.txt
@@ -19,7 +19,7 @@ XP executables.
 
 Building
 
-There are six build targets: 
+There are four build targets: 
     - Debug_NoLibs and Release_NoLibs do not depend on any third-party libraries. 
     - Debug and Release depend on libewf, libvmdk, libvhdi, and zlib to be built so that E01 images as well as VMDK and VHD virtual machine formats are supported.