diff --git a/bindings/java/jni/Makefile.am b/bindings/java/jni/Makefile.am
index b85e68a191a1a657a69ebb63b14dc2184e1c2ecb..8057baf1cf0608e81604baf8b38ff88eb00a7950 100644
--- a/bindings/java/jni/Makefile.am
+++ b/bindings/java/jni/Makefile.am
@@ -3,7 +3,7 @@ AM_CXXFLAGS += -Wno-unused-command-line-argument -Wno-overloaded-virtual
 EXTRA_DIST = .indent.pro 
 
 lib_LTLIBRARIES = libtsk_jni.la
-libtsk_jni_la_SOURCES = dataModel_SleuthkitJNI.cpp dataModel_SleuthkitJNI.h 
+libtsk_jni_la_SOURCES = dataModel_SleuthkitJNI.cpp dataModel_SleuthkitJNI.h  auto_db_java.h auto_db_java.cpp
 libtsk_jni_la_LIBADD = ../../../tsk/libtsk.la
 
 indent:
diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..09bc0400a6ef6ac2f6c0a687745f9c213daff2d8
--- /dev/null
+++ b/bindings/java/jni/auto_db_java.cpp
@@ -0,0 +1,2096 @@
+/*
+ ** The Sleuth Kit
+ **
+ ** Brian Carrier [carrier <at> sleuthkit [dot] org]
+ ** Copyright (c) 2020 Brian Carrier.  All Rights reserved
+ **
+ ** This software is distributed under the Common Public License 1.0
+ **
+ */
+
+/**
+ * \file auto_db_java.cpp
+ * Contains code to populate database with volume and file system information from a specific image.
+ */
+
+#include "auto_db_java.h"
+#include "jni.h"
+#include "tsk/img/img_writer.h"
+#if HAVE_LIBEWF
+#include "tsk/img/ewf.h"
+#include "tsk/img/tsk_img_i.h"
+#endif
+#include <string.h>
+
+#include <algorithm>
+#include <sstream>
+
+using std::stringstream;
+using std::for_each;
+
+/**
+ */
+TskAutoDbJava::TskAutoDbJava()
+{
+    m_curImgId = 0;
+    m_curVsId = 0;
+    m_curVolId = 0;
+    m_curFsId = 0;
+    m_curFileId = 0;
+    m_curUnallocDirId = 0;
+    m_curDirAddr = 0;
+    m_curDirPath = "";
+    m_vsFound = false;
+    m_volFound = false;
+    m_poolFound = false;
+    m_stopped = false;
+    m_foundStructure = false;
+    m_attributeAdded = false;
+    m_addFileSystems = true;
+    m_noFatFsOrphans = false;
+    m_addUnallocSpace = false;
+    m_minChunkSize = -1;
+    m_maxChunkSize = -1;
+
+    m_jniEnv = NULL;
+
+    tsk_init_lock(&m_curDirPathLock);
+}
+
+TskAutoDbJava::~TskAutoDbJava()
+{
+    closeImage();
+    tsk_deinit_lock(&m_curDirPathLock);
+}
+
+/**
+* Look up all callback method IDs
+* @param jniEnv pointer to java environment this was called from
+* @param jobj the JniDbHelper object this was called from
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) {
+    m_jniEnv = jniEnv;
+    m_javaDbObj = m_jniEnv->NewGlobalRef(jobj);
+
+    jclass localCallbackClass = m_jniEnv->FindClass("org/sleuthkit/datamodel/JniDbHelper");
+    if (localCallbackClass == NULL) {
+        return TSK_ERR;
+    }
+    m_callbackClass = (jclass)m_jniEnv->NewGlobalRef(localCallbackClass);
+
+    m_addImageMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageInfo", "(IJLjava/lang/String;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J");
+    if (m_addImageMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addImageNameMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageName", "(JLjava/lang/String;J)I");
+    if (m_addImageNameMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addVolumeSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVsInfo", "(JIJJ)J");
+    if (m_addVolumeSystemMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addVolumeMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVolume", "(JJJJLjava/lang/String;J)J");
+    if (m_addVolumeMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addPoolMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addPool", "(JI)J");
+    if (m_addPoolMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+
+    m_addFileSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFileSystem", "(JJIJJJJJ)J");
+    if (m_addFileSystemMethodID == NULL) {
+        return TSK_ERR;
+    }
+
+    m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;)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;
+    }
+
+    m_addLayoutFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addLayoutFile", "(JJJILjava/lang/String;J)J");
+    if (m_addLayoutFileMethodID == NULL) {
+        return TSK_ERR;
+    }
+    
+    m_addLayoutFileRangeMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addLayoutFileRange", "(JJJJ)J");
+    if (m_addLayoutFileRangeMethodID == NULL) {
+        return TSK_ERR;
+    }
+    return TSK_OK;
+}
+
+/**
+* Cache a database object for later use. Should be called on image, volume system, volume,
+* pool, and file system.
+* @param objId    The object ID of the new object
+* @param parObjId The object ID of the new object's parent
+* @param type     The type of object
+*/
+void 
+TskAutoDbJava::saveObjectInfo(uint64_t objId, uint64_t parObjId, TSK_DB_OBJECT_TYPE_ENUM type) {
+    TSK_DB_OBJECT objectInfo;
+    objectInfo.objId = objId;
+    objectInfo.parObjId = parObjId;
+    objectInfo.type = type;
+    m_savedObjects.push_back(objectInfo);
+}
+
+/**
+* Get a previously cached database object.
+* @param objId The object ID of the object being loaded
+*/
+TSK_RETVAL_ENUM 
+TskAutoDbJava::getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info) {
+    for (vector<TSK_DB_OBJECT>::iterator itObjs = m_savedObjects.begin();
+            itObjs != m_savedObjects.end(); ++itObjs) {
+        TSK_DB_OBJECT* tskDbObj = &(*itObjs);
+        if (tskDbObj->objId == objId) {
+            *obj_info = tskDbObj;
+            return TSK_OK;
+        }
+    }
+
+    // Object not found
+    return TSK_ERR;
+}
+
+/**
+* Adds image details to the existing database tables. Object ID for new image stored in objId.
+*
+* @param type Image type
+* @param ssize Size of device sector in bytes (or 0 for default)
+* @param objId The object id assigned to the image (out param)
+* @param timeZone The timezone the image is from
+* @param size The size of the image in bytes.
+* @param md5 MD5 hash of the image
+* @param sha1 SHA1 hash of the image
+* @param sha256 SHA256 hash of the image
+* @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+* @param collectionDetails collection details
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const string & timezone, TSK_OFF_T size, const string &md5,
+    const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails) {
+
+    const char *tz_cstr = timezone.c_str();
+    jstring tzj = m_jniEnv->NewStringUTF(tz_cstr);
+
+    const char *md5_cstr = md5.c_str();
+    jstring md5j = m_jniEnv->NewStringUTF(md5_cstr);
+
+    const char *sha1_cstr = sha1.c_str();
+    jstring sha1j = m_jniEnv->NewStringUTF(sha1_cstr);
+
+    const char *sha256_cstr = sha256.c_str();
+    jstring sha256j = m_jniEnv->NewStringUTF(sha256_cstr);
+
+    const char *devId_cstr = deviceId.c_str();
+    jstring devIdj = m_jniEnv->NewStringUTF(devId_cstr);
+
+    const char *coll_cstr = collectionDetails.c_str();
+    jstring collj = m_jniEnv->NewStringUTF(coll_cstr);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addImageMethodID,
+        type, ssize, tzj, size, md5j, sha1j, sha256j, devIdj, collj);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    saveObjectInfo(objId, 0, TSK_DB_OBJECT_TYPE_IMG);
+    return TSK_OK;
+}
+
+/**
+* Adds one image name
+* @param objId    The object ID of the image
+* @param imgName  The image name
+* @param sequence The sequence number for this image name
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addImageName(int64_t objId, char const* imgName, int sequence) {
+
+    jstring imgNamej = m_jniEnv->NewStringUTF(imgName);
+
+    jint res = m_jniEnv->CallIntMethod(m_javaDbObj, m_addImageNameMethodID,
+        objId, imgNamej, (int64_t)sequence);
+
+    if (res == 0) {
+        return TSK_OK;
+    }
+    else {
+        return TSK_ERR;
+    }
+}
+
+/**
+* Adds volume system to database. Object ID for new vs stored in objId.
+*
+* @param vs_info  Struct containing info for this volume system
+* @param parObjId Parent object ID for the volume system
+* @param objId    Object ID of new volume system
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addVsInfo(const TSK_VS_INFO* vs_info, int64_t parObjId, int64_t& objId) {
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeSystemMethodID,
+        parObjId, vs_info->vstype, vs_info->offset, (uint64_t)vs_info->block_size);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Save the vs info to use for unallocated blocks later
+    TSK_DB_VS_INFO vs_db;
+    vs_db.objId = objId;
+    vs_db.offset = vs_info->offset;
+    vs_db.vstype = vs_info->vstype;
+    vs_db.block_size = vs_info->block_size;
+    m_savedVsInfo.push_back(vs_db);
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VS);
+    return TSK_OK;
+}
+
+/**
+* Adds pool and pool volume system to database. Object ID for new pool vs stored in objId.
+*
+* @param pool_info  Struct containing info for this pool
+* @param parObjId   Parent object ID for the pool
+* @param objId      Object ID of new pool volume system
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addPoolInfoAndVS(const TSK_POOL_INFO *pool_info, int64_t parObjId, int64_t& objId) {
+
+    // Add the pool
+    jlong poolObjIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addPoolMethodID,
+        parObjId, pool_info->ctype);
+    int64_t poolObjId = (int64_t)poolObjIdj;
+
+    if (poolObjId < 0) {
+        return TSK_ERR;
+    }
+    saveObjectInfo(poolObjId, parObjId, TSK_DB_OBJECT_TYPE_POOL);
+
+    // Add the pool volume
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeSystemMethodID,
+        poolObjIdj, TSK_VS_TYPE_APFS, pool_info->img_offset, (uint64_t)pool_info->block_size);
+    objId = (int64_t)objIdj;
+
+    saveObjectInfo(objId, poolObjId, TSK_DB_OBJECT_TYPE_VS);
+    return TSK_OK;
+}
+
+/**
+* Adds a pool volume to database. Object ID for new pool volume stored in objId.
+*
+* @param pool_vol  Struct containing info for this pool volume
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new pool volume
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addPoolVolumeInfo(const TSK_POOL_VOLUME_INFO* pool_vol,
+    int64_t parObjId, int64_t& objId) {
+
+    jstring descj = m_jniEnv->NewStringUTF(pool_vol->desc);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID,
+        parObjId, (int64_t)pool_vol->index, pool_vol->block, pool_vol->num_blocks,
+        descj, pool_vol->flags);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VOL);
+    return TSK_OK;
+}
+
+/**
+* Adds a volume to database. Object ID for new volume stored in objId.
+*
+* @param vs_part   Struct containing info for this volume
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new volume
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addVolumeInfo(const TSK_VS_PART_INFO* vs_part,
+    int64_t parObjId, int64_t& objId) {
+
+    jstring descj = m_jniEnv->NewStringUTF(vs_part->desc);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID,
+        parObjId, (uint64_t)vs_part->addr, vs_part->start, vs_part->len,
+        descj, vs_part->flags);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Save the volume info for creating unallocated blocks later
+    TSK_DB_VS_PART_INFO vs_part_db;
+    vs_part_db.objId = objId;
+    vs_part_db.addr = vs_part->addr;
+    vs_part_db.start = vs_part->start;
+    vs_part_db.len = vs_part->len;
+    strncpy(vs_part_db.desc, vs_part->desc, TSK_MAX_DB_VS_PART_INFO_DESC_LEN - 1);
+    vs_part_db.flags = vs_part->flags;
+    m_savedVsPartInfo.push_back(vs_part_db);
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VOL);
+    return TSK_OK;
+}
+
+/**
+* Adds a file system to database. Object ID for new file system stored in objId.
+*
+* @param fs_info   Struct containing info for this file system
+* @param parObjId  Parent object ID
+* @param objId     Object ID of new file system
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addFsInfo(const TSK_FS_INFO* fs_info, int64_t parObjId,
+    int64_t& objId) {
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileSystemMethodID,
+        parObjId, fs_info->offset, (int)fs_info->ftype, (uint64_t)fs_info->block_size,
+        fs_info->block_count, fs_info->root_inum, fs_info->first_inum,
+        fs_info->last_inum);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Save the file system info for creating unallocated blocks later
+    TSK_DB_FS_INFO fs_info_db;
+    fs_info_db.objId = objId;
+    fs_info_db.imgOffset = fs_info->offset;
+    fs_info_db.fType = fs_info->ftype;
+    fs_info_db.block_size = fs_info->block_size;
+    fs_info_db.block_count = fs_info->block_count;
+    fs_info_db.root_inum = fs_info->root_inum;
+    fs_info_db.first_inum = fs_info->first_inum;
+    fs_info_db.last_inum = fs_info->last_inum;
+    m_savedFsInfo.push_back(fs_info_db);
+
+    saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_FS);
+    return TSK_OK;
+}
+
+/**
+* Adds a file to database. Object ID for new file stored in objId.
+*
+* @param fs_file
+* @param fs_attr
+* @param path      File path
+* @param parObjId  Parent object ID
+* @param fsObjId   Object ID of the file system
+* @param objId     Object ID of new file
+* @param dataSourceObjId  Object ID of the data source
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addFsFile(TSK_FS_FILE* fs_file,
+    const TSK_FS_ATTR* fs_attr, const char* path,
+    int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId) {
+
+    if (fs_file->name == NULL)
+        return TSK_ERR;
+
+    // Find the object id for the parent folder.
+    int64_t parObjId = 0;
+
+    // Root directory's parent should be the file system object.
+    // Make sure it doesn't have a name, so that we don't pick up ".." entries
+    if ((fs_file->fs_info->root_inum == fs_file->name->meta_addr) &&
+        ((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);
+}
+
+/**
+* Extract the extension from the given file name and store it in the supplied string.
+* @param name A file name
+* @param extension The file name extension will be extracted to extension.
+*/
+void extractExtension(char *name, char *extension) {
+    char *ext = strrchr(name, '.');
+
+    //if ext is not null and is not the entire filename...
+    if (ext && (name != ext)) {
+        size_t extLen = strlen(ext);
+        //... and doesn't only contain the '.' and isn't too long to be a real extension.
+        if ((1 < extLen) && (extLen < 15)) {
+            strncpy(extension, ext + 1, extLen - 1);
+            //normalize to lower case, only works for ascii
+            for (int i = 0; extension[i]; i++) {
+                extension[i] = tolower(extension[i]);
+            }
+        }
+    }
+}
+
+/**
+* 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.
+*
+* @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 dataSourceObjId  Object ID of the data source
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+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)
+{
+    time_t mtime = 0;
+    time_t crtime = 0;
+    time_t ctime = 0;
+    time_t atime = 0;
+    TSK_OFF_T size = 0;
+    int meta_type = 0;
+    int meta_flags = 0;
+    int meta_mode = 0;
+    int gid = 0;
+    int uid = 0;
+    int type = TSK_FS_ATTR_TYPE_NOT_FOUND;
+    int idx = 0;
+
+    if (fs_file->name == NULL)
+        return TSK_OK;
+
+    if (fs_file->meta) {
+        mtime = fs_file->meta->mtime;
+        atime = fs_file->meta->atime;
+        ctime = fs_file->meta->ctime;
+        crtime = fs_file->meta->crtime;
+        meta_type = fs_file->meta->type;
+        meta_flags = fs_file->meta->flags;
+        meta_mode = fs_file->meta->mode;
+        gid = fs_file->meta->gid;
+        uid = fs_file->meta->uid;
+    }
+
+    size_t attr_nlen = 0;
+    if (fs_attr) {
+        type = fs_attr->type;
+        idx = fs_attr->id;
+        size = fs_attr->size;
+        if (fs_attr->name) {
+            if ((fs_attr->type != TSK_FS_ATTR_TYPE_NTFS_IDXROOT) ||
+                (strcmp(fs_attr->name, "$I30") != 0)) {
+                attr_nlen = strlen(fs_attr->name);
+            }
+        }
+    }
+
+    // sanity check
+    if (size < 0) {
+        size = 0;
+    }
+
+    // combine name and attribute name
+    size_t len = strlen(fs_file->name->name);
+    char * name;
+    size_t nlen = len + attr_nlen + 11; // Extra space for possible colon and '-slack'
+    if ((name = (char *)tsk_malloc(nlen)) == NULL) {
+        return TSK_ERR;
+    }
+
+    strncpy(name, fs_file->name->name, nlen);
+
+    char extension[24] = "";
+    extractExtension(name, extension);
+
+    // Add the attribute name
+    if (attr_nlen > 0) {
+        strncat(name, ":", nlen - strlen(name));
+        strncat(name, fs_attr->name, nlen - strlen(name));
+    }
+
+    // clean up path
+    // +2 = space for leading slash and terminating null
+    size_t path_len = strlen(path) + 2;
+    char* escaped_path;
+    if ((escaped_path = (char *)tsk_malloc(path_len)) == NULL) {
+        free(name);
+        return TSK_ERR;
+    }
+    strncpy(escaped_path, "/", path_len);
+    strncat(escaped_path, path, path_len - strlen(escaped_path));
+
+    jstring namej = m_jniEnv->NewStringUTF(name);
+    jstring pathj = m_jniEnv->NewStringUTF(escaped_path);
+    jstring extj = m_jniEnv->NewStringUTF(extension);
+ 
+    // Add the file to the database
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID,
+        parObjId, fsObjId,
+        dataSourceObjId,
+        TSK_DB_FILES_TYPE_FS,
+        type, idx, namej,
+        fs_file->name->meta_addr, (uint64_t)fs_file->name->meta_seq,
+        fs_file->name->type, meta_type, fs_file->name->flags, meta_flags,
+        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;
+
+    if (objId < 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 ".."
+    //   - Data is non-resident
+    //   - The allocated size is greater than the initialized file size
+    //     See github issue #756 on why initsize and not size.
+    //   - The data is not compressed
+    if ((fs_attr != NULL)
+        && ((strlen(name) > 0) && (!TSK_FS_ISDOT(name)))
+        && (!(fs_file->meta->flags & TSK_FS_META_FLAG_COMP))
+        && (fs_attr->flags & TSK_FS_ATTR_NONRES)
+        && (fs_attr->nrd.allocsize > fs_attr->nrd.initsize)) {
+        strncat(name, "-slack", 6);
+        if (strlen(extension) > 0) {
+            strncat(extension, "-slack", 6);
+        }
+        jstring slackNamej = m_jniEnv->NewStringUTF(name);
+        jstring slackExtj = m_jniEnv->NewStringUTF(extension);
+        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,
+            parObjId, fsObjId,
+            dataSourceObjId,
+            TSK_DB_FILES_TYPE_SLACK,
+            type, idx, slackNamej,
+            fs_file->name->meta_addr, (uint64_t)fs_file->name->meta_seq,
+            TSK_FS_NAME_TYPE_REG, TSK_FS_META_TYPE_REG, fs_file->name->flags, meta_flags,
+            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;
+
+        if (slackObjId < 0) {
+            return TSK_ERR;
+        }
+    }
+
+    free(name);
+    free(escaped_path);
+
+    return TSK_OK;
+}
+
+// Internal function object to check for range overlap
+typedef struct _checkFileLayoutRangeOverlap {
+    const vector<TSK_DB_FILE_LAYOUT_RANGE> & ranges;
+    bool hasOverlap;
+
+    explicit _checkFileLayoutRangeOverlap(const vector<TSK_DB_FILE_LAYOUT_RANGE> & ranges)
+        : ranges(ranges), hasOverlap(false) {}
+
+    bool getHasOverlap() const { return hasOverlap; }
+    void operator() (const TSK_DB_FILE_LAYOUT_RANGE & range) {
+        if (hasOverlap)
+            return; //no need to check other
+
+        uint64_t start = range.byteStart;
+        uint64_t end = start + range.byteLen;
+
+        vector<TSK_DB_FILE_LAYOUT_RANGE>::const_iterator it;
+        for (it = ranges.begin(); it != ranges.end(); ++it) {
+            const TSK_DB_FILE_LAYOUT_RANGE * otherRange = &(*it);
+            if (&range == otherRange)
+                continue; //skip, it's the same range
+            uint64_t otherStart = otherRange->byteStart;
+            uint64_t otherEnd = otherStart + otherRange->byteLen;
+            if (start <= otherEnd && end >= otherStart) {
+                hasOverlap = true;
+                break;
+            }
+        }
+    }
+
+} checkFileLayoutRangeOverlap;
+
+/**
+* Internal helper method to add unalloc, unused and carved files with layout ranges to db
+* Generates file_name and populates tsk_files, tsk_objects and tsk_file_layout tables
+* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table
+* @param dbFileType  Type of file
+* @param parentObjId Id of the parent object in the database (fs, volume, or image)
+* @param fsObjId     parent fs, or NULL if the file is not associated with fs
+* @param size        Number of bytes in file
+* @param ranges      vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in)
+* @param objId       object id of the file object created (output)
+* @param dataSourceObjId  The object ID for the data source
+* @returns TSK_OK on success or TSK_ERR on error.
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::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,
+    int64_t dataSourceObjId) {
+
+    const size_t numRanges = ranges.size();
+
+    if (numRanges < 1) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        tsk_error_set_errstr("Error addFileWithLayoutRange() - no ranges present");
+        return TSK_ERR;
+    }
+
+    stringstream fileNameSs;
+    switch (dbFileType) {
+    case TSK_DB_FILES_TYPE_UNALLOC_BLOCKS:
+        fileNameSs << "Unalloc";
+        break;
+
+    case TSK_DB_FILES_TYPE_UNUSED_BLOCKS:
+        fileNameSs << "Unused";
+        break;
+
+    case TSK_DB_FILES_TYPE_CARVED:
+        fileNameSs << "Carved";
+        break;
+    default:
+        stringstream sserr;
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        sserr << "Error addFileWithLayoutRange() - unsupported file type for file layout range: ";
+        sserr << (int)dbFileType;
+        tsk_error_set_errstr("%s", sserr.str().c_str());
+        return TSK_ERR;
+    }
+
+    //ensure layout ranges are sorted (to generate file name and to be inserted in sequence order)
+    sort(ranges.begin(), ranges.end());
+
+    //dome some checking
+    //ensure there is no overlap and each range has unique byte range
+    const checkFileLayoutRangeOverlap & overlapRes =
+        for_each(ranges.begin(), ranges.end(), checkFileLayoutRangeOverlap(ranges));
+    if (overlapRes.getHasOverlap()) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_AUTO_DB);
+        tsk_error_set_errstr("Error addFileWithLayoutRange() - overlap detected between ranges");
+        return TSK_ERR;
+    }
+
+    //construct filename with parent obj id, start byte of first range, end byte of last range
+    fileNameSs << "_" << parentObjId << "_" << ranges[0].byteStart;
+    fileNameSs << "_" << (ranges[numRanges - 1].byteStart + ranges[numRanges - 1].byteLen);
+
+    jstring namej = m_jniEnv->NewStringUTF(fileNameSs.str().c_str());
+
+    // Insert into tsk files and tsk objects
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addLayoutFileMethodID,
+        parentObjId, fsObjId, dataSourceObjId, dbFileType, namej, size);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+
+    // Fill in fileObjId and insert ranges
+    for (vector<TSK_DB_FILE_LAYOUT_RANGE>::iterator it = ranges.begin();
+        it != ranges.end(); ++it) {
+        TSK_DB_FILE_LAYOUT_RANGE & range = *it;
+        range.fileObjId = objId;
+        if (-1 == m_jniEnv->CallLongMethod(m_javaDbObj, m_addLayoutFileRangeMethodID,
+            objId, range.byteStart, range.byteLen, range.sequence)) {
+            return TSK_ERR;
+        }
+    }
+
+    return TSK_OK;
+}
+
+/**
+* Adds information about a unallocated file with layout ranges into the database.
+* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table
+* @param parentObjId Id of the parent object in the database (fs, volume, or image)
+* @param fsObjId parent fs, or NULL if the file is not associated with fs
+* @param size Number of bytes in file
+* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in)
+* @param objId object id of the file object created (output)
+* @param dataSourceObjId The object ID for the data source
+* @returns TSK_OK on success or TSK_ERR on error.
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+    vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+    int64_t dataSourceObjId) {
+    return addFileWithLayoutRange(TSK_DB_FILES_TYPE_UNALLOC_BLOCKS, parentObjId, fsObjId, size, ranges, objId,
+        dataSourceObjId);
+}
+
+/**
+* Adds information about a unused file with layout ranges into the database.
+* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table
+* @param parentObjId Id of the parent object in the database (fs, volume, or image)
+* @param fsObjId parent fs, or NULL if the file is not associated with fs
+* @param size Number of bytes in file
+* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in)
+* @param objId object id of the file object created (output)
+* @param dataSourceObjId The object ID for the data source
+* @returns TSK_OK on success or TSK_ERR on error.
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnusedBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+    vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+    int64_t dataSourceObjId) {
+    return addFileWithLayoutRange(TSK_DB_FILES_TYPE_UNUSED_BLOCKS, parentObjId, fsObjId, size, ranges, objId,
+        dataSourceObjId);
+}
+
+
+
+/**
+* Add a virtual dir to hold unallocated block files for this file system.
+* @param fsObjId  Object ID of the file system
+* @param objId    Object ID of the created virtual dir
+* @param dataSourceObjId  Object ID of the data source
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objId,
+    int64_t dataSourceObjId) {
+
+    const char * const unallocDirName = "$Unalloc";
+    jstring namej = m_jniEnv->NewStringUTF(unallocDirName);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addUnallocParentMethodID,
+        fsObjId, namej);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+    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.
+*
+* @param vol_index The index for the new volume (should be one higher than the number of pool volumes)
+* @param parObjId  The object ID of the parent volume system
+* @param objId     Will be set to the object ID of the new volume
+*
+* @returns TSK_ERR on error, TSK_OK on success
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocatedPoolVolume(int vol_index, int64_t parObjId, int64_t& objId)
+{
+    char *desc = "Unallocated Blocks";
+    jstring descj = m_jniEnv->NewStringUTF(desc);
+
+    jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID,
+        parObjId, vol_index, 0, 0,
+        descj, 0);
+    objId = (int64_t)objIdj;
+
+    if (objId < 0) {
+        return TSK_ERR;
+    }
+    return TSK_OK;
+}
+
+void TskAutoDbJava::close() {
+    if (m_jniEnv == NULL) {
+        return;
+    }
+
+    if (m_javaDbObj != NULL) {
+        m_jniEnv->DeleteGlobalRef(m_javaDbObj);
+    }
+
+    if (m_callbackClass != NULL) {
+        m_jniEnv->DeleteGlobalRef(m_callbackClass);
+    }
+}
+
+int64_t TskAutoDbJava::getImageID() {
+    return m_curImgId;
+}
+
+void TskAutoDbJava::closeImage() {
+    TskAuto::closeImage();
+}
+
+void TskAutoDbJava::setAddFileSystems(bool addFileSystems) {
+    m_addFileSystems = addFileSystems;
+}
+
+void TskAutoDbJava::setNoFatFsOrphans(bool noFatFsOrphans) {
+    m_noFatFsOrphans = noFatFsOrphans;
+}
+
+void TskAutoDbJava::setAddUnallocSpace(bool addUnallocSpace) {
+    setAddUnallocSpace(addUnallocSpace, -1);
+}
+
+void TskAutoDbJava::setAddUnallocSpace(bool addUnallocSpace, int64_t minChunkSize) {
+    m_addUnallocSpace = addUnallocSpace;
+    m_minChunkSize = minChunkSize;
+    m_maxChunkSize = -1;
+}
+
+void TskAutoDbJava::setAddUnallocSpace(int64_t minChunkSize, int64_t maxChunkSize) {
+    m_addUnallocSpace = true;
+    m_minChunkSize = minChunkSize;
+    m_maxChunkSize = maxChunkSize;
+}
+
+/**
+ * Adds an image to the database.
+ *
+ * @param a_num Number of image parts
+ * @param a_images Array of paths to the image parts
+ * @param a_type Image type
+ * @param a_ssize Size of device sector in bytes (or 0 for default)
+ * @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+ * @return 0 for success, 1 for failure
+ */
+uint8_t
+    TskAutoDbJava::openImageUtf8(int a_num, const char *const a_images[],
+    TSK_IMG_TYPE_ENUM a_type, unsigned int a_ssize, const char* a_deviceId)
+{
+    uint8_t retval =
+        TskAuto::openImageUtf8(a_num, a_images, a_type, a_ssize);
+    if (retval != 0) {
+        return retval;
+    }
+
+    if (addImageDetails(a_deviceId)) {
+        return 1;
+    }
+    return 0;
+}
+
+/**
+ * Adds an image to the database.
+ *
+ * @param a_num Number of image parts
+ * @param a_images Array of paths to the image parts
+ * @param a_type Image type
+ * @param a_ssize Size of device sector in bytes (or 0 for default)
+ * @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+ * @return 0 for success, 1 for failure
+ */
+uint8_t
+    TskAutoDbJava::openImage(int a_num, const TSK_TCHAR * const a_images[],
+    TSK_IMG_TYPE_ENUM a_type, unsigned int a_ssize, const char* a_deviceId)
+{
+
+#ifdef TSK_WIN32
+
+    uint8_t retval = TskAuto::openImage(a_num, a_images, a_type, a_ssize);
+
+    if (retval != 0) {
+        return retval;
+    }
+
+    return (addImageDetails(a_deviceId));
+#else
+    return openImageUtf8(a_num, a_images, a_type, a_ssize, a_deviceId);
+#endif
+}
+
+/**
+* Adds an image to the database. Requires that m_img_info is already initialized
+*
+* @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+* @return 0 for success, 1 for failure
+*/
+uint8_t
+TskAutoDbJava::openImage(const char* a_deviceId)
+{
+    if (m_img_info == NULL) {
+        return 1;
+    }
+
+    return(addImageDetails(a_deviceId));
+}
+
+/**
+ * Adds image details to the existing database tables.
+ *
+ * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID).
+ * @return Returns 0 for success, 1 for failure
+ */
+uint8_t
+TskAutoDbJava::addImageDetails(const char* deviceId)
+{
+   string md5 = "";
+   string sha1 = "";
+   string collectionDetails = "";
+#if HAVE_LIBEWF 
+   if (m_img_info->itype == TSK_IMG_TYPE_EWF_EWF) {
+     // @@@ This should really probably be inside of a tsk_img_ method
+       IMG_EWF_INFO *ewf_info = (IMG_EWF_INFO *)m_img_info;
+       if (ewf_info->md5hash_isset) {
+           md5 = ewf_info->md5hash;
+       }
+       if (ewf_info->sha1hash_isset) {
+           sha1 = ewf_info->sha1hash;
+       }
+
+       collectionDetails = ewf_get_details(ewf_info);   
+   }
+#endif
+
+    string devId;
+    if (NULL != deviceId) {
+        devId = deviceId; 
+    } else {
+        devId = "";
+    }
+    if (TSK_OK != addImageInfo(m_img_info->itype, m_img_info->sector_size,
+          m_curImgId, m_curImgTZone, m_img_info->size, md5, sha1, "", devId, collectionDetails)) {
+        registerError();
+        return 1;
+    }
+
+
+
+    char **img_ptrs;
+#ifdef TSK_WIN32
+    // convert image paths to UTF-8
+    img_ptrs = (char **)tsk_malloc(m_img_info->num_img * sizeof(char *));
+    if (img_ptrs == NULL) {
+        return 1;
+    }
+
+    for (int i = 0; i < m_img_info->num_img; i++) {
+        char * img2 = (char*)tsk_malloc(1024 * sizeof(char));
+        UTF8 *ptr8;
+        UTF16 *ptr16;
+
+        ptr8 = (UTF8 *)img2;
+        ptr16 = (UTF16 *)m_img_info->images[i];
+
+        uint8_t retval =
+            tsk_UTF16toUTF8_lclorder((const UTF16 **)&ptr16, (UTF16 *)
+                & ptr16[TSTRLEN(m_img_info->images[i]) + 1], &ptr8,
+                (UTF8 *)((uintptr_t)ptr8 + 1024), TSKlenientConversion);
+        if (retval != TSKconversionOK) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_UNICODE);
+            tsk_error_set_errstr("Error converting image to UTF-8\n");
+            return 1;
+        }
+        img_ptrs[i] = img2;
+    }
+#else 
+    img_ptrs = m_img_info->images;
+#endif
+
+    // Add the image names
+    for (int i = 0; i < m_img_info->num_img; i++) {
+        const char *img_ptr = img_ptrs[i];
+
+        if (TSK_OK != addImageName(m_curImgId, img_ptr, i)) {
+            registerError();
+            return 1;
+        }
+    }
+
+#ifdef TSK_WIN32
+    //cleanup
+    for (int i = 0; i < m_img_info->num_img; ++i) {
+        free(img_ptrs[i]);
+    }
+    free(img_ptrs);
+#endif
+
+    return 0;
+}
+
+
+TSK_FILTER_ENUM 
+TskAutoDbJava::filterVs(const TSK_VS_INFO * vs_info)
+{
+    m_vsFound = true;
+    if (TSK_OK != addVsInfo(vs_info, m_curImgId, m_curVsId)) {
+        registerError();
+        return TSK_FILTER_STOP;
+    }
+
+    return TSK_FILTER_CONT;
+}
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterPool(const TSK_POOL_INFO * pool_info)
+{
+    m_poolFound = true;
+
+    if (m_volFound && m_vsFound) {
+        // there's a volume system and volume
+        if (TSK_OK != addPoolInfoAndVS(pool_info, m_curVolId, m_curPoolVs)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+        // Save the parent obj ID for the pool
+        m_poolOffsetToParentId[pool_info->img_offset] = m_curVolId;
+    }
+    else {
+        // pool doesn't live in a volume, use image as parent
+        if (TSK_OK != addPoolInfoAndVS(pool_info, m_curImgId, m_curPoolVs)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+        // Save the parent obj ID for the pool
+        m_poolOffsetToParentId[pool_info->img_offset] = m_curImgId;
+    }
+
+    // Store the volume system object ID for later use
+    m_poolOffsetToVsId[pool_info->img_offset] = m_curPoolVs;
+
+    return TSK_FILTER_CONT;
+}
+
+/**
+* Adds unallocated pool blocks to a new volume.
+*
+* @param numPool Will be updated with the number of pools processed
+*
+* @return Returns 0 for success, 1 for failure
+*/
+TSK_RETVAL_ENUM
+TskAutoDbJava::addUnallocatedPoolBlocksToDb(size_t & numPool) {
+
+    for (int i = 0; i < m_poolInfos.size(); i++) {
+        const TSK_POOL_INFO * pool_info = m_poolInfos[i];
+        if (m_poolOffsetToVsId.find(pool_info->img_offset) == m_poolOffsetToVsId.end()) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_DB);
+            tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - could not find volume system object ID for pool at offset %lld", pool_info->img_offset);
+            return TSK_ERR;
+        }
+        int64_t curPoolVs = m_poolOffsetToVsId[pool_info->img_offset];
+
+        /* Make sure  the pool_info is still allocated */
+        if (pool_info->tag != TSK_POOL_INFO_TAG) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_DB);
+            tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - pool_info is not allocated");
+            return TSK_ERR;
+        }
+
+        /* Only APFS pools are currently supported */
+        if (pool_info->ctype != TSK_POOL_TYPE_APFS) {
+            continue;
+        }
+
+        /* Increment the count of pools found */
+        numPool++;
+
+        /* Create the volume */
+        int64_t unallocVolObjId;
+        if (TSK_ERR == addUnallocatedPoolVolume(pool_info->num_vols, curPoolVs, unallocVolObjId)) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_AUTO_DB);
+            tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - error createing unallocated space pool volume");
+            return TSK_ERR;
+        }
+
+        /* Create the unallocated space files */
+        TSK_FS_ATTR_RUN * unalloc_runs = tsk_pool_unallocated_runs(pool_info);
+        TSK_FS_ATTR_RUN * current_run = unalloc_runs;
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;
+        while (current_run != NULL) {
+
+            TSK_DB_FILE_LAYOUT_RANGE tempRange(current_run->addr * pool_info->block_size, current_run->len * pool_info->block_size, 0);
+
+            ranges.push_back(tempRange);
+            int64_t fileObjId = 0;
+            if (TSK_ERR == addUnallocBlockFile(unallocVolObjId, NULL, current_run->len * pool_info->block_size, ranges, fileObjId, m_curImgId)) {
+                registerError();
+                tsk_fs_attr_run_free(unalloc_runs);
+                return TSK_ERR;
+            }
+
+            current_run = current_run->next;
+            ranges.clear();
+        }
+        tsk_fs_attr_run_free(unalloc_runs);
+    }
+
+    return TSK_OK;
+}
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol)
+{
+
+    if (TSK_OK != addPoolVolumeInfo(pool_vol, m_curPoolVs, m_curPoolVol)) {
+        registerError();
+        return TSK_FILTER_STOP;
+    }
+
+    return TSK_FILTER_CONT;
+}
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterVol(const TSK_VS_PART_INFO * vs_part)
+{
+    m_volFound = true;
+    m_foundStructure = true;
+    m_poolFound = false;
+
+    if (TSK_OK != addVolumeInfo(vs_part, m_curVsId, m_curVolId)) {
+        registerError();
+        return TSK_FILTER_STOP;
+    }
+
+    return TSK_FILTER_CONT;
+}
+
+
+TSK_FILTER_ENUM
+TskAutoDbJava::filterFs(TSK_FS_INFO * fs_info)
+{
+    TSK_FS_FILE *file_root;
+    m_foundStructure = true;
+
+    if (m_poolFound) {
+        // there's a pool
+        if (TSK_OK != addFsInfo(fs_info, m_curPoolVol, m_curFsId)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+    }
+    else if (m_volFound && m_vsFound) {
+        // there's a volume system and volume
+        if (TSK_OK != addFsInfo(fs_info, m_curVolId, m_curFsId)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+    }
+    else {
+        // file system doesn't live in a volume, use image as parent
+        if (TSK_OK != addFsInfo(fs_info, m_curImgId, m_curFsId)) {
+            registerError();
+            return TSK_FILTER_STOP;
+        }
+    }
+
+
+    // We won't hit the root directory on the walk, so open it now 
+    if ((file_root = tsk_fs_file_open(fs_info, NULL, "/")) != NULL) {
+        processFile(file_root, "");
+        tsk_fs_file_close(file_root);
+        file_root = NULL;
+    }
+
+
+    // make sure that flags are set to get all files -- we need this to
+    // find parent directory
+     
+    TSK_FS_DIR_WALK_FLAG_ENUM filterFlags = (TSK_FS_DIR_WALK_FLAG_ENUM)
+        (TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC);
+
+    //check if to skip processing of FAT orphans
+    if (m_noFatFsOrphans 
+        && TSK_FS_TYPE_ISFAT(fs_info->ftype) ) {
+            filterFlags = (TSK_FS_DIR_WALK_FLAG_ENUM) (filterFlags | TSK_FS_DIR_WALK_FLAG_NOORPHAN);
+    }
+
+    setFileFilterFlags(filterFlags);
+
+    return TSK_FILTER_CONT;
+}
+
+/* Insert the file data into the file table.
+ * @param fs_file
+ * @param fs_attr
+ * @param path
+ * Returns TSK_ERR on error.
+ */
+TSK_RETVAL_ENUM
+    TskAutoDbJava::insertFileData(TSK_FS_FILE * fs_file,
+    const TSK_FS_ATTR * fs_attr, const char *path)
+{
+    if (-1 == addFsFile(fs_file, fs_attr, path, m_curFsId, m_curFileId,
+            m_curImgId)) {
+        registerError();
+        return TSK_ERR;
+    }
+
+    return TSK_OK;
+}
+
+/**
+ * Analyzes the open image and adds image info to a database.
+ * Does not deal with transactions and such.  Refer to startAddImage()
+ * for more control. 
+ * @returns 1 if a critical error occurred (DB doesn't exist, no file system, etc.), 2 if errors occurred at some point adding files to the DB (corrupt file, etc.), and 0 otherwise.  Errors will have been registered.
+ */
+uint8_t TskAutoDbJava::addFilesInImgToDb()
+{
+    // @@@ This seems bad because we are overriding what the user may
+    // have set. We should remove the public API if we are going to 
+    // override it -- presumably this was added so that we always have
+    // unallocated volume space...
+    setVolFilterFlags((TSK_VS_PART_FLAG_ENUM) (TSK_VS_PART_FLAG_ALLOC |
+            TSK_VS_PART_FLAG_UNALLOC));
+
+    uint8_t retVal = 0;
+    if (findFilesInImg()) {
+        // map the boolean return value from findFiles to the three-state return value we use
+        // @@@ findFiles should probably return this three-state enum too
+        if (m_foundStructure == false) {
+            retVal = 1;
+        }
+        else {
+            retVal = 2;
+        }
+    }
+
+    TSK_RETVAL_ENUM addUnallocRetval = TSK_OK;
+    if (m_addUnallocSpace)
+        addUnallocRetval = addUnallocSpaceToDb();
+
+    // findFiles return value trumps unalloc since it can return either 2 or 1.
+    if (retVal) {
+        return retVal;
+    }
+    else if (addUnallocRetval == TSK_ERR) {
+        return 2;
+    }
+    else {
+        return 0;
+    }
+}
+
+
+/**
+ * Start the process to add image/file metadata to database inside of a transaction. 
+ * User must call either commitAddImage() to commit the changes,
+ * or revertAddImage() to revert them.
+ *
+ * @param numImg Number of image parts
+ * @param imagePaths Array of paths to the image parts
+ * @param imgType Image type
+ * @param sSize Size of device sector in bytes (or 0 for default)
+ * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID)
+ * @return 0 for success, 1 for failure
+ */
+uint8_t
+    TskAutoDbJava::startAddImage(int numImg, const TSK_TCHAR * const imagePaths[],
+    TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId)
+{
+    if (tsk_verbose)
+        tsk_fprintf(stderr, "TskAutoDbJava::startAddImage: Starting add image process\n");
+
+    if (openImage(numImg, imagePaths, imgType, sSize, deviceId)) {
+        tsk_error_set_errstr2("TskAutoDbJava::startAddImage");
+        registerError();
+        return 1;
+    }
+
+    if (m_imageWriterEnabled) {
+        tsk_img_writer_create(m_img_info, m_imageWriterPath);
+    }
+    
+    if (m_addFileSystems) {
+        return addFilesInImgToDb();
+    } else {
+        return 0;
+    }
+}
+
+/**
+* Start the process to add image/file metadata to database inside of a transaction.
+* User must call either commitAddImage() to commit the changes,
+* or revertAddImage() to revert them.
+*
+* @param img_info Previously initialized TSK_IMG_INFO object
+* @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID)
+* @return 0 for success, 1 for failure
+*/
+uint8_t
+TskAutoDbJava::startAddImage(TSK_IMG_INFO * img_info, const char* deviceId)
+{
+    openImageHandle(img_info);
+
+    if (m_img_info == NULL) {
+        return 1;
+    }
+
+    if (tsk_verbose)
+        tsk_fprintf(stderr, "TskAutoDbJava::startAddImage: Starting add image process\n");
+
+    if (openImage(deviceId)) {
+        tsk_error_set_errstr2("TskAutoDbJava::startAddImage");
+        registerError();
+        return 1;
+    }
+
+    if (m_imageWriterEnabled) {
+        if (tsk_img_writer_create(m_img_info, m_imageWriterPath)) {
+            registerError();
+            return 1;
+        }
+    }
+
+    if (m_addFileSystems) {
+        return addFilesInImgToDb();
+    }
+    else {
+        return 0;
+    }
+}
+
+
+#ifdef WIN32
+/**
+ * Start the process to add image/file metadata to database inside of a transaction. 
+ * Same functionality as addFilesInImgToDb().  Reverts
+ * all changes on error. User must call either commitAddImage() to commit the changes,
+ * or revertAddImage() to revert them.
+ *
+ * @param numImg Number of image parts
+ * @param imagePaths Array of paths to the image parts
+ * @param imgType Image type
+ * @param sSize Size of device sector in bytes (or 0 for default)
+ * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID)
+ * @return 0 for success 1, for failure
+ */
+uint8_t
+    TskAutoDbJava::startAddImage(int numImg, const char *const imagePaths[],
+    TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId)
+{
+    if (tsk_verbose) 
+        tsk_fprintf(stderr, "TskAutoDbJava::startAddImage_utf8: Starting add image process\n");
+
+    if (openImageUtf8(numImg, imagePaths, imgType, sSize, deviceId)) {
+        tsk_error_set_errstr2("TskAutoDbJava::startAddImage");
+        registerError();
+        return 1;
+    }
+    if (m_imageWriterEnabled) {
+        tsk_img_writer_create(m_img_info, m_imageWriterPath);
+    }
+
+    if (m_addFileSystems) {
+        return addFilesInImgToDb();
+    } else {
+        return 0;
+    }
+}
+#endif
+
+
+/**
+ * Cancel the running process.  Will not be handled immediately. 
+ */
+void
+ TskAutoDbJava::stopAddImage()
+{
+    if (tsk_verbose)
+        tsk_fprintf(stderr, "TskAutoDbJava::stopAddImage: Stop request received\n");
+    
+    m_stopped = true;
+    setStopProcessing();
+    // flag is checked every time processFile() is called
+}
+
+/**
+ * Set the current image's timezone
+ */
+void
+TskAutoDbJava::setTz(string tzone)
+{
+    m_curImgTZone = tzone;
+}
+
+TSK_RETVAL_ENUM
+TskAutoDbJava::processFile(TSK_FS_FILE * fs_file, const char *path)
+{
+    // Check if the process has been canceled
+     if (m_stopped) {
+        if (tsk_verbose)
+            tsk_fprintf(stderr, "TskAutoDbJava::processFile: Stop request detected\n");
+        return TSK_STOP;
+    }
+
+    /* Update the current directory, which can be used to show
+     * progress.  If we get a directory, then use its name.  We
+     * do this so that when we are searching for orphan files, then
+     * we at least show $OrphanFiles as status.  The secondary check
+     * is to grab the parent folder from files once we return back 
+     * into a folder when we are doing our depth-first recursion. */
+    if (isDir(fs_file)) {
+        m_curDirAddr = fs_file->name->meta_addr;
+        tsk_take_lock(&m_curDirPathLock);
+        m_curDirPath = string(path) + fs_file->name->name;
+        tsk_release_lock(&m_curDirPathLock);
+    }
+    else if (m_curDirAddr != fs_file->name->par_addr) {
+        m_curDirAddr = fs_file->name->par_addr;
+        tsk_take_lock(&m_curDirPathLock);
+        m_curDirPath = path;
+        tsk_release_lock(&m_curDirPathLock);
+    }
+
+    /* process the attributes.  The case of having 0 attributes can occur
+     * with virtual / sparse files and HFS directories.  
+     * At some point, this can probably be cleaned
+     * up if TSK is more consistent about if there should always be an 
+     * attribute or not.  Sometimes, none of the attributes are added
+     * because of their type and we always want to add a reference to 
+     * every file. */
+    TSK_RETVAL_ENUM retval = TSK_OK;
+    m_attributeAdded = false;
+    if (tsk_fs_file_attr_getsize(fs_file) > 0) {
+        retval = processAttributes(fs_file, path);
+    }
+
+    // insert a general row if we didn't add a specific attribute one
+    if ((retval == TSK_OK) && (m_attributeAdded == false)) {
+        retval = insertFileData(fs_file, NULL, path);
+    }
+    
+    // reset the file id
+    m_curFileId = 0;
+
+    if (retval == TSK_STOP)
+        return TSK_STOP;
+    else 
+        return TSK_OK;
+}
+
+
+// we return only OK or STOP -- errors are registered only and OK is returned. 
+TSK_RETVAL_ENUM
+TskAutoDbJava::processAttribute(TSK_FS_FILE * fs_file,
+    const TSK_FS_ATTR * fs_attr, const char *path)
+{
+    // add the file metadata for the default attribute type
+    if (isDefaultType(fs_file, fs_attr)) {
+
+        if (insertFileData(fs_attr->fs_file, fs_attr, path) == TSK_ERR) {
+            registerError();
+            return TSK_OK;
+        }
+        else {
+            m_attributeAdded = true;
+        }
+    }
+
+    return TSK_OK;
+}
+
+
+/**
+* Callback invoked per every unallocated block in the filesystem
+* Creates file ranges and file entries 
+* A single file entry per consecutive range of blocks
+* @param a_block block being walked
+* @param a_ptr a pointer to an UNALLOC_BLOCK_WLK_TRACK struct
+* @returns TSK_WALK_CONT if continue, otherwise TSK_WALK_STOP if stop processing requested
+*/
+TSK_WALK_RET_ENUM TskAutoDbJava::fsWalkUnallocBlocksCb(const TSK_FS_BLOCK *a_block, void *a_ptr) {
+    UNALLOC_BLOCK_WLK_TRACK * unallocBlockWlkTrack = (UNALLOC_BLOCK_WLK_TRACK *) a_ptr;
+
+    if (unallocBlockWlkTrack->tskAutoDbJava.m_stopAllProcessing)
+        return TSK_WALK_STOP;
+
+    // initialize if this is the first block
+    if (unallocBlockWlkTrack->isStart) {
+        unallocBlockWlkTrack->isStart = false;
+        unallocBlockWlkTrack->curRangeStart = a_block->addr;
+        unallocBlockWlkTrack->prevBlock = a_block->addr;
+        unallocBlockWlkTrack->size = unallocBlockWlkTrack->fsInfo.block_size;
+        unallocBlockWlkTrack->nextSequenceNo = 0;
+        return TSK_WALK_CONT;
+    }
+
+    // We want to keep consecutive blocks in the same run, so simply update prevBlock and the size
+    // if this one is consecutive with the last call. But, if we have hit the max chunk
+    // size, then break up this set of consecutive blocks.
+    if ((a_block->addr == unallocBlockWlkTrack->prevBlock + 1) && ((unallocBlockWlkTrack->maxChunkSize <= 0) ||
+            (unallocBlockWlkTrack->size < unallocBlockWlkTrack->maxChunkSize))) {
+        unallocBlockWlkTrack->prevBlock = a_block->addr;
+		unallocBlockWlkTrack->size += unallocBlockWlkTrack->fsInfo.block_size;
+        return TSK_WALK_CONT;
+    }
+
+    // this block is not contiguous with the previous one or we've hit the maximum size; create and add a range object
+    const uint64_t rangeStartOffset = unallocBlockWlkTrack->curRangeStart * unallocBlockWlkTrack->fsInfo.block_size 
+        + unallocBlockWlkTrack->fsInfo.offset;
+    const uint64_t rangeSizeBytes = (1 + unallocBlockWlkTrack->prevBlock - unallocBlockWlkTrack->curRangeStart) 
+        * unallocBlockWlkTrack->fsInfo.block_size;
+    unallocBlockWlkTrack->ranges.push_back(TSK_DB_FILE_LAYOUT_RANGE(rangeStartOffset, rangeSizeBytes, unallocBlockWlkTrack->nextSequenceNo++));
+
+    // Return (instead of adding this run) if we are going to:
+    // a) Make one big file with all unallocated space (minChunkSize == 0)
+    // or
+    // b) Only make an unallocated file once we have at least chunkSize bytes
+    // of data in our current run (minChunkSize > 0)
+    // In either case, reset the range pointers and add this block to the size
+    if ((unallocBlockWlkTrack->minChunkSize == 0) ||
+        ((unallocBlockWlkTrack->minChunkSize > 0) &&
+        (unallocBlockWlkTrack->size < unallocBlockWlkTrack->minChunkSize))) {
+
+        unallocBlockWlkTrack->size += unallocBlockWlkTrack->fsInfo.block_size;
+        unallocBlockWlkTrack->curRangeStart = a_block->addr;
+        unallocBlockWlkTrack->prevBlock = a_block->addr;
+        return TSK_WALK_CONT;
+    }
+    
+    // at this point we are either chunking and have reached the chunk limit
+    // or we're not chunking. Either way we now add what we've got to the DB
+    int64_t fileObjId = 0;
+    TskAutoDbJava & tskAutoDbJava = unallocBlockWlkTrack->tskAutoDbJava;
+    if (-1 == tskAutoDbJava.addUnallocBlockFile(tskAutoDbJava.m_curUnallocDirId,
+        unallocBlockWlkTrack->fsObjId, unallocBlockWlkTrack->size, unallocBlockWlkTrack->ranges, fileObjId, tskAutoDbJava.m_curImgId) == TSK_ERR) {
+            // @@@ Handle error -> Don't have access to registerError() though...
+    }
+
+    // reset
+    unallocBlockWlkTrack->curRangeStart = a_block->addr;
+    unallocBlockWlkTrack->prevBlock = a_block->addr;
+    unallocBlockWlkTrack->size = unallocBlockWlkTrack->fsInfo.block_size; // The current block is part of the new range
+    unallocBlockWlkTrack->ranges.clear();
+    unallocBlockWlkTrack->nextSequenceNo = 0;
+
+    //we don't know what the last unalloc block is in advance
+    //and will handle the last range in addFsInfoUnalloc()
+    
+    return TSK_WALK_CONT;
+}
+
+
+/**
+* Add unallocated space for the given file system to the database.
+* Create files for consecutive unalloc block ranges.
+* @param dbFsInfo fs to process
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addFsInfoUnalloc(const TSK_DB_FS_INFO & dbFsInfo) {
+
+    // Unalloc space is handled separately for APFS
+    if (dbFsInfo.fType == TSK_FS_TYPE_APFS) {
+        return TSK_OK;
+    }
+
+    //open the fs we have from database
+    TSK_FS_INFO * fsInfo = tsk_fs_open_img(m_img_info, dbFsInfo.imgOffset, dbFsInfo.fType);
+    if (fsInfo == NULL) {
+        tsk_error_set_errstr2("TskAutoDbJava::addFsInfoUnalloc: error opening fs at offset %" PRIdOFF, dbFsInfo.imgOffset);
+        registerError();
+        return TSK_ERR;
+    }
+
+    //create a "fake" dir to hold the unalloc files for the fs
+    if (-1 == addUnallocFsBlockFilesParent(dbFsInfo.objId, m_curUnallocDirId, m_curImgId) == TSK_ERR) {
+        tsk_error_set_errstr2("addFsInfoUnalloc: error creating dir for unallocated space");
+        registerError();
+        return TSK_ERR;
+    }
+
+    //walk unalloc blocks on the fs and process them
+    //initialize the unalloc block walk tracking 
+    UNALLOC_BLOCK_WLK_TRACK unallocBlockWlkTrack(*this, *fsInfo, dbFsInfo.objId, m_minChunkSize, m_maxChunkSize);
+    uint8_t block_walk_ret = tsk_fs_block_walk(fsInfo, fsInfo->first_block, fsInfo->last_block, (TSK_FS_BLOCK_WALK_FLAG_ENUM)(TSK_FS_BLOCK_WALK_FLAG_UNALLOC | TSK_FS_BLOCK_WALK_FLAG_AONLY), 
+        fsWalkUnallocBlocksCb, &unallocBlockWlkTrack);
+
+    if (block_walk_ret == 1) {
+        stringstream errss;
+        tsk_fs_close(fsInfo);
+        errss << "TskAutoDbJava::addFsInfoUnalloc: error walking fs unalloc blocks, fs id: ";
+        errss << unallocBlockWlkTrack.fsObjId;
+        tsk_error_set_errstr2("%s", errss.str().c_str());
+        registerError();
+        return TSK_ERR;
+    }
+
+    if(m_stopAllProcessing) {
+        tsk_fs_close(fsInfo);
+        return TSK_OK;
+    }
+
+    // handle creation of the last range
+    // make range inclusive from curBlockStart to prevBlock
+    const uint64_t byteStart = unallocBlockWlkTrack.curRangeStart * fsInfo->block_size + fsInfo->offset;
+    const uint64_t byteLen = (1 + unallocBlockWlkTrack.prevBlock - unallocBlockWlkTrack.curRangeStart) * fsInfo->block_size;
+    unallocBlockWlkTrack.ranges.push_back(TSK_DB_FILE_LAYOUT_RANGE(byteStart, byteLen, unallocBlockWlkTrack.nextSequenceNo++));
+    int64_t fileObjId = 0;
+
+    if (-1 == addUnallocBlockFile(m_curUnallocDirId, dbFsInfo.objId, unallocBlockWlkTrack.size, unallocBlockWlkTrack.ranges, fileObjId, m_curImgId) == TSK_ERR) {
+        registerError();
+        tsk_fs_close(fsInfo);
+        return TSK_ERR;
+    }
+    
+    //cleanup 
+    tsk_fs_close(fsInfo);
+
+    return TSK_OK; 
+}
+
+/**
+* Process all unallocated space for this disk image and create "virtual" files with layouts
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocSpaceToDb() {
+    if (m_stopAllProcessing) {
+        return TSK_OK;
+    }
+
+    size_t numVsP = 0;
+    size_t numFs = 0;
+    size_t numPool = 0;
+
+    TSK_RETVAL_ENUM retFsSpace = addUnallocFsSpaceToDb(numFs);
+    TSK_RETVAL_ENUM retVsSpace = addUnallocVsSpaceToDb(numVsP);
+    TSK_RETVAL_ENUM retPoolSpace = addUnallocatedPoolBlocksToDb(numPool);
+
+    //handle case when no fs and no vs partitions and no pools
+    TSK_RETVAL_ENUM retImgFile = TSK_OK;
+    if (numVsP == 0 && numFs == 0 && numPool == 0) {
+        retImgFile = addUnallocImageSpaceToDb();
+    }
+    
+    
+    if (retFsSpace == TSK_ERR || retVsSpace == TSK_ERR || retPoolSpace == TSK_ERR || retImgFile == TSK_ERR)
+        return TSK_ERR;
+    else
+        return TSK_OK;
+}
+
+
+/**
+* Process each file system in the database and add its unallocated sectors to virtual files. 
+* @param numFs (out) number of filesystems found
+* @returns TSK_OK on success, TSK_ERR on error (if some or all fs could not be processed)
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocFsSpaceToDb(size_t & numFs) {
+
+    if(m_stopAllProcessing) {
+        return TSK_OK;
+    }
+
+    numFs = m_savedFsInfo.size();
+    TSK_RETVAL_ENUM allFsProcessRet = TSK_OK;
+    for (vector<TSK_DB_FS_INFO>::iterator it = m_savedFsInfo.begin(); it!= m_savedFsInfo.end(); ++it) {
+        if (m_stopAllProcessing) {
+            break;
+        }
+        if (addFsInfoUnalloc(*it) == TSK_ERR)
+            allFsProcessRet = TSK_ERR;
+    }
+
+    //TODO set parent_path for newly created virt dir/file hierarchy for consistency
+
+    return allFsProcessRet;
+    return TSK_OK;
+}
+
+/**
+* Process each volume in the database and add its unallocated sectors to virtual files. 
+* @param numVsP (out) number of vs partitions found
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocVsSpaceToDb(size_t & numVsP) {
+
+    numVsP = m_savedVsPartInfo.size();
+
+    //get fs infos to see if this vspart has fs
+    for (vector<TSK_DB_VS_PART_INFO>::const_iterator it = m_savedVsPartInfo.begin();
+            it != m_savedVsPartInfo.end(); ++it) {
+        if (m_stopAllProcessing) {
+            break;
+        }
+        const TSK_DB_VS_PART_INFO &vsPart = *it;
+
+        //interested in unalloc, meta, or alloc and no fs
+        if ( (vsPart.flags & (TSK_VS_PART_FLAG_UNALLOC | TSK_VS_PART_FLAG_META)) == 0 ) {
+            //check if vspart has no fs
+            bool hasFs = false;
+            for (vector<TSK_DB_FS_INFO>::const_iterator itFs = m_savedFsInfo.begin();
+               itFs != m_savedFsInfo.end(); ++itFs) {
+               const TSK_DB_FS_INFO & fsInfo = *itFs;
+
+               TSK_DB_OBJECT* fsObjInfo = NULL;
+               if (getObjectInfo(fsInfo.objId, &fsObjInfo) == TSK_ERR ) {
+                   stringstream errss;
+                   errss << "addUnallocVsSpaceToDb: error getting object info for fs from db, objId: " << fsInfo.objId;
+                   tsk_error_set_errstr2("%s", errss.str().c_str());
+                   registerError();
+                   return TSK_ERR;
+               }
+
+               if (fsObjInfo->parObjId == vsPart.objId) {
+                   hasFs = true;
+                   break;
+               }
+            }
+        
+            if (hasFs == true) {
+                //skip processing this vspart
+                continue;
+            }
+
+            // Check if the volume contains a pool
+            bool hasPool = false;
+            for (std::map<int64_t, int64_t>::iterator iter = m_poolOffsetToParentId.begin(); iter != m_poolOffsetToParentId.end(); ++iter) {
+                if (iter->second == vsPart.objId) {
+                    hasPool = true;
+                }
+            }
+            if (hasPool) {
+                // Skip processing this vspart
+                continue;
+            }
+
+        }
+
+        // Get sector size and image offset from parent vs info
+        // Get parent id of this vs part
+        TSK_DB_OBJECT* vsPartObj = NULL;     
+        if (getObjectInfo(vsPart.objId, &vsPartObj) == TSK_ERR) {
+            stringstream errss;
+            errss << "addUnallocVsSpaceToDb: error getting object info for vs part from db, objId: " << vsPart.objId;
+            tsk_error_set_errstr2("%s", errss.str().c_str());
+            registerError();
+            return TSK_ERR;
+        }
+        if (vsPartObj == NULL) {
+            return TSK_ERR;
+        }
+
+        TSK_DB_VS_INFO* vsInfo = NULL;
+        for (vector<TSK_DB_VS_INFO>::iterator itVs = m_savedVsInfo.begin();
+                itVs != m_savedVsInfo.end(); ++itVs) {
+            TSK_DB_VS_INFO* temp_vs_info = &(*itVs);
+            if (temp_vs_info->objId == vsPartObj->parObjId) {
+                vsInfo = temp_vs_info;
+            }
+        }
+
+        if (vsInfo == NULL ) {
+            stringstream errss;
+            errss << "addUnallocVsSpaceToDb: error getting volume system info from db, objId: " << vsPartObj->parObjId;
+            tsk_error_set_errstr2("%s", errss.str().c_str());
+            registerError();
+            return TSK_ERR;
+        }
+
+        // Create an unalloc file with unalloc part, with vs part as parent
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;
+        const uint64_t byteStart = vsInfo->offset + vsInfo->block_size * vsPart.start;
+        const uint64_t byteLen = vsInfo->block_size * vsPart.len; 
+        TSK_DB_FILE_LAYOUT_RANGE tempRange(byteStart, byteLen, 0);
+        ranges.push_back(tempRange);
+        int64_t fileObjId = 0;
+        if (addUnallocBlockFile(vsPart.objId, 0, tempRange.byteLen, ranges, fileObjId, m_curImgId) == TSK_ERR) {
+            registerError();
+            return TSK_ERR;
+        }
+    }
+
+    return TSK_OK;
+}
+
+
+/**
+* Adds unalloc space for the image if there is no volumes and no file systems.
+*
+* @returns TSK_OK on success, TSK_ERR on error
+*/
+TSK_RETVAL_ENUM TskAutoDbJava::addUnallocImageSpaceToDb() {
+
+    const TSK_OFF_T imgSize = getImageSize();
+    if (imgSize == -1) {
+        tsk_error_set_errstr("addUnallocImageSpaceToDb: error getting current image size, can't create unalloc block file for the image.");
+        registerError();
+        return TSK_ERR;
+    }
+    else {
+        TSK_DB_FILE_LAYOUT_RANGE tempRange(0, imgSize, 0);
+        //add unalloc block file for the entire image
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;
+        ranges.push_back(tempRange);
+        int64_t fileObjId = 0;
+        if (-1 == addUnallocBlockFile(m_curImgId, 0, imgSize, ranges, fileObjId, m_curImgId)) {
+            return TSK_ERR;
+        }
+    }
+    return TSK_OK;
+}
+
+/**
+* Returns the directory currently being analyzed by processFile().
+* Safe to use from another thread than processFile().
+*
+* @returns curDirPath string representing currently analyzed directory
+*/
+const std::string TskAutoDbJava::getCurDir() {
+    string curDirPath;
+    tsk_take_lock(&m_curDirPathLock);
+    curDirPath = m_curDirPath;
+    tsk_release_lock(&m_curDirPathLock);
+    return curDirPath;
+}
diff --git a/bindings/java/jni/auto_db_java.h b/bindings/java/jni/auto_db_java.h
new file mode 100644
index 0000000000000000000000000000000000000000..85a9dc69eb1d006032ef6a2ba7470dfd563f95ae
--- /dev/null
+++ b/bindings/java/jni/auto_db_java.h
@@ -0,0 +1,247 @@
+/*
+ ** The Sleuth Kit 
+ **
+ ** Brian Carrier [carrier <at> sleuthkit [dot] org]
+ ** Copyright (c) 2020 Brian Carrier.  All Rights reserved
+ **
+ ** This software is distributed under the Common Public License 1.0
+ **
+ */
+
+/**
+ * \file auto_db_java.h
+ * Contains the class that creates a case-level database of file system
+ * data from the JNI code.
+ */
+
+#ifndef _AUTO_DB_JAVA_H
+#define _AUTO_DB_JAVA_H
+
+#include <map>
+using std::map;
+
+#include <string>
+using std::string;
+
+#include "tsk/auto/tsk_auto_i.h"
+#include "tsk/auto/tsk_db.h"
+#include "jni.h"
+
+
+/** \internal
+ * C++ class that implements TskAuto to load file metadata into a database. 
+ * This is used by the TskCaseDb class. 
+ */
+class TskAutoDbJava :public TskAuto {
+  public:
+    TskAutoDbJava();
+    virtual ~TskAutoDbJava();
+    virtual uint8_t openImage(int, const TSK_TCHAR * const images[],
+        TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL);
+    virtual uint8_t openImage(const char* a_deviceId = NULL);
+    virtual uint8_t openImageUtf8(int, const char *const images[],
+        TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL);
+    virtual void closeImage();
+    void close();
+    virtual void setTz(string tzone);
+
+    virtual TSK_FILTER_ENUM filterVs(const TSK_VS_INFO * vs_info);
+    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 TSK_RETVAL_ENUM processFile(TSK_FS_FILE * fs_file,
+        const char *path);
+    const std::string getCurDir();
+
+    /**
+     * Sets whether or not the file systems for an image should be added when 
+     * the image is added to the case database. The default value is true. 
+     */
+    void setAddFileSystems(bool addFileSystems);
+
+    /**
+     * Skip processing of orphans on FAT filesystems.  
+     * This will make the loading of the database much faster
+     * but you will not have all deleted files.  Default value is false. 
+     * @param noFatFsOrphans flag set to true if to skip processing orphans on FAT fs
+     */
+    virtual void setNoFatFsOrphans(bool noFatFsOrphans);
+
+    /**
+     * When enabled, records for unallocated file system space will be added to the database. Default value is false.
+     * @param addUnallocSpace If true, create records for contiguous unallocated file system sectors. 
+     */
+    virtual void setAddUnallocSpace(bool addUnallocSpace);
+
+    /**
+     * When enabled, records for unallocated file system space will be added to the database. Default value is false.
+     * @param addUnallocSpace If true, create records for contiguous unallocated file system sectors.
+     * @param minChunkSize the number of bytes to group unallocated data into. A value of 0 will create
+     * one large chunk and group only on volume boundaries. A value of -1 will group each consecutive
+     * chunk.
+     */
+    virtual void setAddUnallocSpace(bool addUnallocSpace, int64_t minChunkSize);
+
+    /**
+    * When enabled, records for unallocated file system space will be added to the database with the given parameters.
+    * Automatically sets the flag to create records for contiguous unallocated file system sectors.
+    * @param minChunkSize the number of bytes to group unallocated data into. A value of 0 will create
+    * one large chunk and group only on volume boundaries. A value of -1 will group each consecutive
+    * chunk.
+    * @param maxChunkSize the maximum number of bytes in one record of unallocated data. A value of -1 will not
+    * split the records based on size
+    */
+    virtual void setAddUnallocSpace(int64_t minChunkSize, int64_t maxChunkSize);
+
+    uint8_t addFilesInImgToDb();
+
+    /**
+     * 
+     */
+    uint8_t startAddImage(int numImg, const TSK_TCHAR * const imagePaths[],
+        TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId = NULL);
+    uint8_t startAddImage(TSK_IMG_INFO * img_info, const char* deviceId = NULL);
+#ifdef WIN32
+    uint8_t startAddImage(int numImg, const char *const imagePaths[],
+        TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId = NULL);
+#endif
+    void stopAddImage();
+
+    int64_t getImageID();
+
+    TSK_RETVAL_ENUM initializeJni(JNIEnv *, jobject);
+
+  private:
+    int64_t m_curImgId;     ///< Object ID of image currently being processed
+    int64_t m_curVsId;      ///< Object ID of volume system currently being processed
+    int64_t m_curVolId;     ///< Object ID of volume currently being processed
+    int64_t m_curPoolVol;   ///< Object ID of the pool volume currently being processed
+    int64_t m_curPoolVs;    ///< Object ID of the pool volume system currently being processed
+    int64_t m_curFsId;      ///< Object ID of file system currently being processed
+    int64_t m_curFileId;    ///< Object ID of file currently being processed
+    TSK_INUM_T m_curDirAddr;		///< Meta address the directory currently being processed
+    int64_t m_curUnallocDirId;	
+    string m_curDirPath;		//< Path of the current directory being processed
+    tsk_lock_t m_curDirPathLock; //< protects concurrent access to m_curDirPath
+    string m_curImgTZone;
+    bool m_vsFound;
+    bool m_volFound;
+    bool m_poolFound;
+    bool m_stopped;
+    bool m_addFileSystems;
+    bool m_noFatFsOrphans;
+    bool m_addUnallocSpace;
+    int64_t m_minChunkSize; ///< -1 for no minimum, 0 for no chunking at all, greater than 0 to wait for that number of chunks before writing to the database 
+    int64_t m_maxChunkSize; ///< Max number of unalloc bytes to process before writing to the database, even if there is no natural break. -1 for no chunking
+    bool m_foundStructure;  ///< Set to true when we find either a volume or file system
+    bool m_attributeAdded; ///< Set to true when an attribute was added by processAttributes
+
+    // These are used to write unallocated blocks for pools at the end of the add image
+    // process. We can't load the pool_info objects directly from the database so we will
+    // store info about them here.
+    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;
+    jobject m_javaDbObj = NULL;
+    jmethodID m_addImageMethodID = NULL;
+    jmethodID m_addImageNameMethodID = NULL;
+    jmethodID m_addVolumeSystemMethodID = NULL;
+    jmethodID m_addVolumeMethodID = NULL;
+    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;
+
+    // Cached objects
+    vector<TSK_DB_FS_INFO> m_savedFsInfo;
+    vector<TSK_DB_VS_INFO> m_savedVsInfo;
+    vector<TSK_DB_VS_PART_INFO> m_savedVsPartInfo;
+    vector<TSK_DB_OBJECT> m_savedObjects;
+
+    void saveObjectInfo(uint64_t objId, uint64_t parObjId, TSK_DB_OBJECT_TYPE_ENUM type);
+    TSK_RETVAL_ENUM getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info);
+
+    // prevent copying until we add proper logic to handle it
+    TskAutoDbJava(const TskAutoDbJava&);
+    TskAutoDbJava & operator=(const TskAutoDbJava&);
+
+    //internal structure to keep track of temp. unalloc block range
+    typedef struct _UNALLOC_BLOCK_WLK_TRACK {
+        _UNALLOC_BLOCK_WLK_TRACK(TskAutoDbJava & tskAutoDbJava, const TSK_FS_INFO & fsInfo, const int64_t fsObjId, int64_t minChunkSize, int64_t maxChunkSize)
+            : tskAutoDbJava(tskAutoDbJava),fsInfo(fsInfo),fsObjId(fsObjId),curRangeStart(0), minChunkSize(minChunkSize), maxChunkSize(maxChunkSize), prevBlock(0), isStart(true), nextSequenceNo(0) {}
+        TskAutoDbJava & tskAutoDbJava;
+        const TSK_FS_INFO & fsInfo;
+        const int64_t fsObjId;
+        vector<TSK_DB_FILE_LAYOUT_RANGE> ranges;																																										
+        TSK_DADDR_T curRangeStart;
+        int64_t size;
+        const int64_t minChunkSize;
+        const int64_t maxChunkSize;
+        TSK_DADDR_T prevBlock;
+        bool isStart;
+        uint32_t nextSequenceNo;
+    } UNALLOC_BLOCK_WLK_TRACK;
+
+    uint8_t addImageDetails(const char *);
+    TSK_RETVAL_ENUM insertFileData(TSK_FS_FILE * fs_file,
+        const TSK_FS_ATTR *, const char *path);
+    virtual TSK_RETVAL_ENUM processAttribute(TSK_FS_FILE *,
+        const TSK_FS_ATTR * fs_attr, const char *path);
+
+    TSK_RETVAL_ENUM addUnallocatedPoolBlocksToDb(size_t & numPool);
+    static TSK_WALK_RET_ENUM fsWalkUnallocBlocksCb(const TSK_FS_BLOCK *a_block, void *a_ptr);
+    TSK_RETVAL_ENUM addFsInfoUnalloc(const TSK_DB_FS_INFO & dbFsInfo);
+    TSK_RETVAL_ENUM addUnallocFsSpaceToDb(size_t & numFs);
+    TSK_RETVAL_ENUM addUnallocVsSpaceToDb(size_t & numVsP);
+    TSK_RETVAL_ENUM addUnallocImageSpaceToDb();
+    TSK_RETVAL_ENUM addUnallocSpaceToDb();
+
+    // JNI methods
+    TSK_RETVAL_ENUM addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const string & timezone, TSK_OFF_T size, const string &md5,
+        const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails);
+    TSK_RETVAL_ENUM addImageName(int64_t objId, char const* imgName, int sequence);
+    TSK_RETVAL_ENUM addVsInfo(const TSK_VS_INFO* vs_info, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addPoolInfoAndVS(const TSK_POOL_INFO *pool_info, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addPoolVolumeInfo(const TSK_POOL_VOLUME_INFO* pool_vol, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addVolumeInfo(const TSK_VS_PART_INFO* vs_part, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addFsInfo(const TSK_FS_INFO* fs_info, int64_t parObjId, int64_t& objId);
+    TSK_RETVAL_ENUM addFsFile(TSK_FS_FILE* fs_file,
+        const TSK_FS_ATTR* fs_attr, const char* path,
+        int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId);
+    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);
+    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,
+        int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnallocBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+        vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+        int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnusedBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size,
+        vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId,
+        int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId);
+    TSK_RETVAL_ENUM addUnallocatedPoolVolume(int vol_index, int64_t parObjId, int64_t& objId);
+
+};
+
+#endif
diff --git a/bindings/java/jni/dataModel_SleuthkitJNI.cpp b/bindings/java/jni/dataModel_SleuthkitJNI.cpp
index 9572b510cb9f631f0f92886b2947c8b8b87c6dd3..b9160399769732c37d230ffeca93a1dc8bc428c9 100644
--- a/bindings/java/jni/dataModel_SleuthkitJNI.cpp
+++ b/bindings/java/jni/dataModel_SleuthkitJNI.cpp
@@ -14,6 +14,7 @@
 #include "tsk/auto/tsk_is_image_supported.h"
 #include "tsk/img/img_writer.h"
 #include "tsk/img/raw.h"
+#include "auto_db_java.h"
 #include "jni.h"
 #include "dataModel_SleuthkitJNI.h"
 #include <locale.h>
@@ -957,12 +958,12 @@ JNIEXPORT jobject JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_hashDbLookup
  * @param addUnallocSpace Pass true to create virtual files for unallocated space. Ignored if addFileSystems is false.
  * @param skipFatFsOrphans Pass true to skip processing of orphan files for FAT file systems. Ignored if addFileSystems is false.
  *
- * @return A pointer to the process (TskAutoDb object) or NULL on error.
+ * @return A pointer to the process (TskAutoDbJava object) or NULL on error.
  */
 JNIEXPORT jlong JNICALL
     Java_org_sleuthkit_datamodel_SleuthkitJNI_initAddImgNat(JNIEnv * env,
-    jclass obj, jlong caseHandle, jstring timeZone, jboolean addUnallocSpace, jboolean skipFatFsOrphans) {
-    return Java_org_sleuthkit_datamodel_SleuthkitJNI_initializeAddImgNat(env, obj, caseHandle, timeZone, true, addUnallocSpace, skipFatFsOrphans);
+    jclass obj, jlong caseHandle, jobject callbackObj, jstring timeZone, jboolean addUnallocSpace, jboolean skipFatFsOrphans) {
+    return Java_org_sleuthkit_datamodel_SleuthkitJNI_initializeAddImgNat(env, obj, caseHandle, callbackObj, timeZone, true, addUnallocSpace, skipFatFsOrphans);
 }
 
 /*
@@ -976,19 +977,13 @@ JNIEXPORT jlong JNICALL
  * @param addUnallocSpace Pass true to create virtual files for unallocated space. Ignored if addFileSystems is false.
  * @param skipFatFsOrphans Pass true to skip processing of orphan files for FAT file systems. Ignored if addFileSystems is false.
  *
- * @return A pointer to the process (TskAutoDb object) or NULL on error.
+ * @return A pointer to the process (TskAutoDbJava object) or NULL on error.
  */
 JNIEXPORT jlong JNICALL
 Java_org_sleuthkit_datamodel_SleuthkitJNI_initializeAddImgNat(JNIEnv * env, jclass obj,
-    jlong caseHandle, jstring timeZone, jboolean addFileSystems, jboolean addUnallocSpace, jboolean skipFatFsOrphans) {
+    jlong caseHandle, jobject callbackObj, jstring timeZone, jboolean addFileSystems, jboolean addUnallocSpace, jboolean skipFatFsOrphans) {
     jboolean isCopy;
 
-    TskCaseDb *tskCase = castCaseDb(env, caseHandle);
-    if (tskCase == 0) {
-        //exception already set
-        return 0;
-    }
-
     if (env->GetStringUTFLength(timeZone) > 0) {
         const char *tzstr = env->GetStringUTFChars(timeZone, &isCopy);
 
@@ -1016,35 +1011,35 @@ Java_org_sleuthkit_datamodel_SleuthkitJNI_initializeAddImgNat(JNIEnv * env, jcla
         TZSET();
     }
 
-    TskAutoDb *tskAuto = tskCase->initAddImage();
-    if (tskAuto == NULL) {
-        setThrowTskCoreError(env, "Error getting tskAuto handle from initAddImage");
+    TskAutoDbJava *tskAutoJava = new TskAutoDbJava();
+    if (tskAutoJava == NULL) {
+        setThrowTskCoreError(env, "Error creating TskAutoDbJava");
         return 0;
     }
 
     // set the options flags
-    tskAuto->setAddFileSystems(addFileSystems?true:false);
+    tskAutoJava->setAddFileSystems(addFileSystems?true:false);
     if (addFileSystems) {
         if (addUnallocSpace) {
             // Minimum size of unalloc files: 500 MB, maximum size: 1 GB
-            tskAuto->setAddUnallocSpace((int64_t)500 * 1024 * 1024, (int64_t)1024 * 1024 * 1024);
+            tskAutoJava->setAddUnallocSpace((int64_t)500 * 1024 * 1024, (int64_t)1024 * 1024 * 1024);
         }
         else {
-            tskAuto->setAddUnallocSpace(false);
+            tskAutoJava->setAddUnallocSpace(false);
         }
-        tskAuto->setNoFatFsOrphans(skipFatFsOrphans?true:false);
+        tskAutoJava->setNoFatFsOrphans(skipFatFsOrphans?true:false);
     } else {
-        tskAuto->setAddUnallocSpace(false);
-        tskAuto->setNoFatFsOrphans(true);
+        tskAutoJava->setAddUnallocSpace(false);
+        tskAutoJava->setNoFatFsOrphans(true);
     }
 
-    // we don't use the block map and it slows it down
-    tskAuto->createBlockMap(false);
-
-    // ingest modules calc hashes
-    tskAuto->hashFiles(false);
+    // Set up the callbacks
+    if (TSK_ERR == tskAutoJava->initializeJni(env, callbackObj)) {
+        setThrowTskCoreError(env, "Error initializing JNI callbacks");
+        return 0;
+    }
 
-    return (jlong) tskAuto;
+    return (jlong)tskAutoJava;
 }
 
 /*
@@ -1064,10 +1059,10 @@ JNIEXPORT void JNICALL
     Java_org_sleuthkit_datamodel_SleuthkitJNI_runOpenAndAddImgNat(JNIEnv * env,
     jclass obj, jlong process, jstring deviceId, jobjectArray paths, jint numImgs, jstring timeZone) {
 
-    TskAutoDb *tskAuto = ((TskAutoDb *) process);
+    TskAutoDbJava *tskAuto = ((TskAutoDbJava *) process);
     if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
         setThrowTskCoreError(env, 
-            "runAddImgNat: Invalid TskAutoDb object passed in");
+            "runAddImgNat: Invalid TskAutoDbJava object passed in");
         return;
     }
 
@@ -1122,18 +1117,12 @@ JNIEXPORT void JNICALL
         }
 
         if (ret == 1) {
-            //fatal error
+            // Fatal error
             setThrowTskCoreError(env, msgss.str().c_str());
         }
         else if (ret == 2) {
-            if(tskAuto->isDbOpen()) {
-                // if we can still talk to the database, it's a non-fatal error
-                setThrowTskDataError(env, msgss.str().c_str());
-            }
-            else {
-                // we cannot talk to the database, fatal error
-                setThrowTskCoreError(env, msgss.str().c_str());
-            }
+            // Non-fatal error
+            setThrowTskDataError(env, msgss.str().c_str());
         }
     }
 
@@ -1152,7 +1141,7 @@ JNIEXPORT void JNICALL
     free(imagepaths8);
     env->ReleaseStringUTFChars(deviceId, (const char *) device_id);
 
-    // if process completes successfully, must call revertAddImgNat or commitAddImgNat to free the TskAutoDb
+    // // Must call finishAddImgNat to free the TskAutoDb
 }
 
 /*
@@ -1171,10 +1160,10 @@ JNIEXPORT void JNICALL
 Java_org_sleuthkit_datamodel_SleuthkitJNI_runAddImgNat(JNIEnv * env,
     jclass obj, jlong process, jstring deviceId, jlong a_img_info, jstring timeZone, jstring imageWriterPathJ) {
     
-    TskAutoDb *tskAuto = ((TskAutoDb *)process);
+    TskAutoDbJava *tskAuto = ((TskAutoDbJava *)process);
     if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
         setThrowTskCoreError(env,
-            "runAddImgNat: Invalid TskAutoDb object passed in");
+            "runAddImgNat: Invalid TskAutoDbJava object passed in");
         return;
     }
 
@@ -1226,18 +1215,12 @@ Java_org_sleuthkit_datamodel_SleuthkitJNI_runAddImgNat(JNIEnv * env,
         }
 
         if (ret == 1) {
-            //fatal error
+            // Fatal error
             setThrowTskCoreError(env, msgss.str().c_str());
         }
         else if (ret == 2) {
-            if (tskAuto->isDbOpen()) {
-                // if we can still talk to the database, it's a non-fatal error
-                setThrowTskDataError(env, msgss.str().c_str());
-            }
-            else {
-                // we cannot talk to the database, fatal error
-                setThrowTskCoreError(env, msgss.str().c_str());
-            }
+            // Non-fatal error
+            setThrowTskDataError(env, msgss.str().c_str());
         }
     }
 
@@ -1248,7 +1231,7 @@ Java_org_sleuthkit_datamodel_SleuthkitJNI_runAddImgNat(JNIEnv * env,
     // Cleanup
     env->ReleaseStringUTFChars(deviceId, (const char *)device_id);
 
-    // if process completes successfully, must call revertAddImgNat or commitAddImgNat to free the TskAutoDb
+    // Must call finishAddImgNat to free the TskAutoDb
 }
 
 
@@ -1261,10 +1244,10 @@ Java_org_sleuthkit_datamodel_SleuthkitJNI_runAddImgNat(JNIEnv * env,
 JNIEXPORT void JNICALL
     Java_org_sleuthkit_datamodel_SleuthkitJNI_stopAddImgNat(JNIEnv * env,
     jclass obj, jlong process) {
-    TskAutoDb *tskAuto = ((TskAutoDb *) process);
+    TskAutoDbJava *tskAuto = ((TskAutoDbJava *) process);
     if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
         setThrowTskCoreError(env,
-            "stopAddImgNat: Invalid TskAutoDb object passed in");
+            "stopAddImgNat: Invalid TskAutoDbJava object passed in");
         return;
     }
     tskAuto->stopAddImage();
@@ -1272,45 +1255,23 @@ JNIEXPORT void JNICALL
 
 
 /*
- * Revert the given add-image process.  Deletes the 'process' handle.
- * @param env pointer to java environment this was called from
- * @param obj the java object this was called from
- * @param process the add-image process created by initAddImgNat
- */
-JNIEXPORT void JNICALL
-    Java_org_sleuthkit_datamodel_SleuthkitJNI_revertAddImgNat(JNIEnv * env,
-    jclass obj, jlong process) {
-    TskAutoDb *tskAuto = ((TskAutoDb *) process);
-    if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
-        setThrowTskCoreError(env,
-            "revertAddImgNat: Invalid TskAutoDb object passed in");
-        return;
-    }
-    if (tskAuto->revertAddImage()) {
-        setThrowTskCoreError(env);
-        return;
-    }
-    delete tskAuto;
-    tskAuto = 0;
-}
-
-
-/*
- * Commit the given add-image process. Deletes the 'process' handle.
- * @param env pointer to java environment this was called from
- * @param obj the java object this was called from
- * @param process the add-image process created by initAddImgNat
- */
+* Completes the given add-image process. Deletes the 'process' handle and
+* returns the ID of the added image.
+* @param env pointer to java environment this was called from
+* @param obj the java object this was called from
+* @param process the add-image process created by initAddImgNat
+*/
 JNIEXPORT jlong JNICALL
-    Java_org_sleuthkit_datamodel_SleuthkitJNI_commitAddImgNat(JNIEnv * env,
+Java_org_sleuthkit_datamodel_SleuthkitJNI_finishAddImgNat(JNIEnv * env,
     jclass obj, jlong process) {
-    TskAutoDb *tskAuto = ((TskAutoDb *) process);
+    TskAutoDbJava *tskAuto = ((TskAutoDbJava *)process);
     if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
         setThrowTskCoreError(env,
-             "commitAddImgNat: Invalid TskAutoDb object passed in");
+            "commitAddImgNat: Invalid TskAutoDb object passed in");
         return -1;
     }
-    int64_t imgId = tskAuto->commitAddImage();
+    int64_t imgId = tskAuto->getImageID();
+    tskAuto->close();
     delete tskAuto;
     tskAuto = 0;
     if (imgId == -1) {
@@ -2150,7 +2111,7 @@ JNIEXPORT jstring JNICALL
     Java_org_sleuthkit_datamodel_SleuthkitJNI_getCurDirNat
     (JNIEnv * env,jclass obj, jlong dbHandle)
 {
-    TskAutoDb *tskAuto = ((TskAutoDb *) dbHandle);
+    TskAutoDbJava *tskAuto = ((TskAutoDbJava *) dbHandle);
     const std::string curDir = tskAuto->getCurDir();
     jstring jdir = (*env).NewStringUTF(curDir.c_str());
     return jdir;
diff --git a/bindings/java/jni/dataModel_SleuthkitJNI.h b/bindings/java/jni/dataModel_SleuthkitJNI.h
index 17bbaa66526e52f9a04d01c970b64148bdb4186c..52e06743f70d20a621e8eeef662966f37812c056 100644
--- a/bindings/java/jni/dataModel_SleuthkitJNI.h
+++ b/bindings/java/jni/dataModel_SleuthkitJNI.h
@@ -210,18 +210,18 @@ JNIEXPORT jobject JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_hashDbLookup
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
  * Method:    initAddImgNat
- * Signature: (JLjava/lang/String;ZZ)J
+ * Signature: (JLorg/sleuthkit/datamodel/JniDbHelper;Ljava/lang/String;ZZ)J
  */
 JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_initAddImgNat
-  (JNIEnv *, jclass, jlong, jstring, jboolean, jboolean);
+  (JNIEnv *, jclass, jlong, jobject, jstring, jboolean, jboolean);
 
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
  * Method:    initializeAddImgNat
- * Signature: (JLjava/lang/String;ZZZ)J
+ * Signature: (JLorg/sleuthkit/datamodel/JniDbHelper;Ljava/lang/String;ZZZ)J
  */
 JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_initializeAddImgNat
-  (JNIEnv *, jclass, jlong, jstring, jboolean, jboolean, jboolean);
+  (JNIEnv *, jclass, jlong, jobject, jstring, jboolean, jboolean, jboolean);
 
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
@@ -249,18 +249,10 @@ JNIEXPORT void JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_stopAddImgNat
 
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
- * Method:    revertAddImgNat
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_revertAddImgNat
-  (JNIEnv *, jclass, jlong);
-
-/*
- * Class:     org_sleuthkit_datamodel_SleuthkitJNI
- * Method:    commitAddImgNat
+ * Method:    finishAddImgNat
  * Signature: (J)J
  */
-JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_commitAddImgNat
+JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_finishAddImgNat
   (JNIEnv *, jclass, jlong);
 
 /*
diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..9efb3f0589c6ce37f326ed9f21ebe9357624eb01
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
@@ -0,0 +1,401 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> 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.datamodel;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
+
+/**
+ * This is a utility class to allow the native C code to write to the 
+ * case database. All callbacks from the native code should come through this class.
+ * Any changes to the method signatures in this class will require changes to the 
+ * native code.
+ */
+class JniDbHelper {
+    
+    private static final Logger logger = Logger.getLogger(JniDbHelper.class.getName());
+    
+    private final SleuthkitCase caseDb;
+    private CaseDbTransaction trans = null;
+    
+    private final Map<Long, Long> fsIdToRootDir = new HashMap<>();
+    
+    JniDbHelper(SleuthkitCase caseDb) {
+        this.caseDb = caseDb;
+        trans = null;
+    }
+    
+    /**
+     * Start the add image transaction
+     * 
+     * @throws TskCoreException 
+     */
+    void beginTransaction() throws TskCoreException {
+        trans = caseDb.beginTransaction();
+    }
+    
+    /**
+     * Commit the add image transaction
+     * 
+     * @throws TskCoreException 
+     */
+    void commitTransaction() throws TskCoreException {
+        trans.commit();
+        trans = null;
+    }
+    
+    /**
+     * Revert the add image transaction
+     * 
+     * @throws TskCoreException 
+     */
+    void revertTransaction() throws TskCoreException {
+        trans.rollback();
+        trans = null;
+    }        
+    
+    /**
+     * Add a new image to the database.
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param type        Type of image.
+     * @param ssize       Sector size.
+     * @param timezone    Time zone.
+     * @param size        Image size.
+     * @param md5         MD5 hash.
+     * @param sha1        SHA1 hash.
+     * @param sha256      SHA256 hash.
+     * @param deviceId    Device ID.
+     * @param collectionDetails  The collection details.
+     * 
+     * @return The object ID of the new image or -1 if an error occurred
+     */
+    long addImageInfo(int type, long ssize, String timezone, 
+            long size, String md5, String sha1, String sha256, String deviceId, 
+            String collectionDetails) {
+        try {
+            return caseDb.addImageJNI(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), ssize, size,
+                    timezone, md5, sha1, sha256, deviceId, collectionDetails, trans);
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding image to the database", ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add an image name to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param objId    The object id of the image.
+     * @param name     The file name for the image
+     * @param sequence The sequence number of this file.
+     * 
+     * @return 0 if successful, -1 if not
+     */
+    int addImageName(long objId, String name, long sequence) {
+        try {
+            caseDb.addImageNameJNI(objId, name, sequence, trans);
+            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);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a volume system to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param vsType
+     * @param imgOffset
+     * @param blockSize
+     * 
+     * @return The object ID of the new volume system or -1 if an error occurred
+     */
+    long addVsInfo(long parentObjId, int vsType, long imgOffset, long blockSize) {
+        try {
+            VolumeSystem vs = caseDb.addVolumeSystem(parentObjId, TskData.TSK_VS_TYPE_ENUM.valueOf(vsType), imgOffset, blockSize, trans);
+            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);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a volume to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param addr
+     * @param start
+     * @param length
+     * @param desc
+     * @param flags
+     * 
+     * @return The object ID of the new volume or -1 if an error occurred
+     */
+    long addVolume(long parentObjId, long addr, long start, long length, String desc,
+            long flags) {
+        try {
+            Volume vol = caseDb.addVolume(parentObjId, addr, start, length, desc, flags, trans);
+            return vol.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding volume to the database - parent object ID: " + parentObjId
+                + ", addr: " + addr, ex);
+            return -1;
+        }
+    }
+
+    /**
+     * Add a pool to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param poolType
+     * 
+     * @return The object ID of the new pool or -1 if an error occurred
+     */
+    long addPool(long parentObjId, int poolType) {
+        try {
+            Pool pool = caseDb.addPool(parentObjId, TskData.TSK_POOL_TYPE_ENUM.valueOf(poolType), trans);
+            return pool.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding pool to the database - parent object ID: " + parentObjId, ex);
+            return -1;
+        }
+    }
+
+    /**
+     * Add a file system to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId
+     * @param imgOffset
+     * @param fsType
+     * @param blockSize
+     * @param blockCount
+     * @param rootInum
+     * @param firstInum
+     * @param lastInum
+     * 
+     * @return The object ID of the new file system or -1 if an error occurred
+     */
+    long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize, long blockCount,
+            long rootInum, long firstInum, long lastInum) {
+        try {
+            FileSystem fs = caseDb.addFileSystem(parentObjId, imgOffset, TskData.TSK_FS_TYPE_ENUM.valueOf(fsType), blockSize, blockCount,
+                    rootInum, firstInum, lastInum, null, trans);
+            return fs.getId();
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error adding file system to the database - parent object ID: " + parentObjId
+                    + ", offset: " + imgOffset, ex);
+            return -1;
+        }
+    }
+
+    /**
+     * Add a file to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId     The parent of the file.
+     * @param fsObjId         The object ID of the file system.
+     * @param dataSourceObjId The data source object ID.
+     * @param fsType    The type.
+     * @param attrType  The type attribute given to the file by the file system.
+     * @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 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
+     *                     the metadata structure of the file system.
+     * @param dirFlags  The allocated status of the file, usually as
+     *                     reported in the name structure of the file system.
+     * @param metaFlags The allocated status of the file, usually as
+     *                     reported in the metadata structure of the file system.
+     * @param size      The file size.
+     * @param crtime    The created time.
+     * @param ctime     The last changed time
+     * @param atime     The last accessed time.
+     * @param mtime     The last modified time.
+     * @param meta_mode The modes for the file.
+     * @param gid       The group identifier.
+     * @param uid       The user identifier.
+     * @param md5       The MD5 hash.
+     * @param known     The file known status.
+     * @param escaped_path The escaped path to the file.
+     * @param extension    The file extension.
+     * 
+     * @return The object ID of the new file or -1 if an error occurred
+     */
+    long addFile(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) {
+        try {
+            long objId = caseDb.addFileJNI(parentObjId, 
+                fsObjId, dataSourceObjId,
+                fsType,
+                attrType, attrId, name,
+                metaAddr, metaSeq,
+                dirType, metaType, dirFlags, metaFlags,
+                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);
+            }
+            return objId;
+        } 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);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a layout file to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param parentObjId     The parent object ID of the layout file.
+     * @param fsObjId         The file system object ID.
+     * @param dataSourceObjId The data source object ID.
+     * @param fileType        The file type.
+     * @param name            The file name.
+     * @param size            The file size.
+     * 
+     * @return The object ID of the new file or -1 if an error occurred
+     */
+    long addLayoutFile(long parentObjId, 
+        long fsObjId, long dataSourceObjId,
+        int fileType,
+        String name, long size) {
+        try {
+            // The file system may be null for layout files
+            Long fsObjIdForDb = fsObjId;
+            if (fsObjId == 0) {
+                fsObjIdForDb = null;
+            }
+            
+            long objId = caseDb.addFileJNI(parentObjId, 
+                fsObjIdForDb, dataSourceObjId,
+                fileType,
+                null, null, name,
+                null, null,
+                TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue(), 
+                TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue(), 
+                TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(), 
+                TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue(),
+                size,
+                null, null, null, null,
+                null, null, null,
+                null, TskData.FileKnown.UNKNOWN,
+                null, null, 
+                true, trans);
+            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);
+            return -1;
+        }
+    }    
+    
+    /**
+     * Add a layout file range to the database. 
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param objId     Object ID of the layout file.
+     * @param byteStart Start byte.
+     * @param byteLen   Length in bytes.
+     * @param seq       Sequence number of this range.
+     * 
+     * @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;
+        }
+    }
+    
+    /**
+     * 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
+     * 
+     * @return The object ID of the parent or -1 if not found
+     */
+    long findParentObjId(long metaAddr, long fsObjId, String path, String name) {
+        try {
+            return caseDb.findParentObjIdJNI(metaAddr, fsObjId, path, name, trans);
+        } catch (TskCoreException ex) {
+            logger.log(Level.WARNING, "Error looking up parent with meta addr: " + metaAddr + " and name " + name, ex);
+            return -1;
+        }
+    }
+    
+    /**
+     * Add a virtual directory to hold unallocated file system blocks.
+     * Intended to be called from the native code during the add image process.
+     * 
+     * @param fsObjId
+     * @param name
+     * 
+     * @return The object ID of the new virtual directory or -1 if an error occurred
+     */
+    long addUnallocFsBlockFilesParent(long fsObjId, String name) {
+        try {
+            if (! fsIdToRootDir.containsKey(fsObjId)) {
+                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);
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Error creating virtual directory " + name + " under file system ID " + fsObjId, ex);
+            return -1;
+        }
+    }
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 8d32c771006cd1a886d83ddea6590dd993210ec7..40bfa848eb68c3ea8beb03a027470a640d16cae3 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -6006,7 +6006,7 @@ public FileSystem addFileSystem(long parentObjId, long imgOffset, TskData.TSK_FS
 			preparedStatement.clearParameters();
 			preparedStatement.setLong(1, newObjId);
 			preparedStatement.setLong(2, imgOffset);
-			preparedStatement.setShort(3, (short) type.getValue());
+			preparedStatement.setInt(3, type.getValue());
 			preparedStatement.setLong(4, blockSize);
 			preparedStatement.setLong(5, blockCount);
 			preparedStatement.setLong(6, rootInum);
@@ -10965,6 +10965,453 @@ private List<IngestModuleInfo> getIngestModules(int ingestJobId, CaseDbConnectio
 
 		}
 	}
+	
+	/**
+	 * Add an image to the database.
+	 * For use with the JNI callbacks associated with the add image process.
+	 *
+	 * @param type        Type of image.
+	 * @param sectorSize  Sector size.
+	 * @param size        Image size.
+	 * @param timezone    Time zone.
+	 * @param md5         MD5 hash.
+	 * @param sha1        SHA1 hash.
+	 * @param sha256      SHA256 hash.
+	 * @param deviceId    Device ID.
+	 * @param collectionDetails Collection details.
+	 * @param transaction Case DB transaction.
+	 *
+	 * @return The newly added Image object ID.
+	 *
+	 * @throws TskCoreException
+	 */
+	long addImageJNI(TskData.TSK_IMG_TYPE_ENUM type, long sectorSize, long size,
+			String timezone, String md5, String sha1, String sha256,
+			String deviceId, String collectionDetails,
+			CaseDbTransaction transaction) throws TskCoreException {
+		acquireSingleUserCaseWriteLock();
+		try {
+			// Insert a row for the Image into the tsk_objects table.
+			CaseDbConnection connection = transaction.getConnection();
+			long newObjId = addObject(0, TskData.ObjectType.IMG.getObjectType(), connection);
+
+			// Add a row to tsk_image_info
+			// INSERT INTO tsk_image_info (obj_id, type, ssize, tzone, size, md5, sha1, sha256, display_name)
+			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_INFO);
+			preparedStatement.clearParameters();
+			preparedStatement.setLong(1, newObjId);
+			preparedStatement.setShort(2, (short) type.getValue());
+			preparedStatement.setLong(3, sectorSize);
+			preparedStatement.setString(4, timezone);
+			//prevent negative size
+			long savedSize = size < 0 ? 0 : size;
+			preparedStatement.setLong(5, savedSize);
+			preparedStatement.setString(6, md5);
+			preparedStatement.setString(7, sha1);
+			preparedStatement.setString(8, sha256);
+			preparedStatement.setString(9, null);
+			connection.executeUpdate(preparedStatement);
+
+			// Add a row to data_source_info
+			preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_DATA_SOURCE_INFO_WITH_ACQ_DETAIL);
+			preparedStatement.setLong(1, newObjId);
+			preparedStatement.setString(2, deviceId);
+			preparedStatement.setString(3, timezone);
+			preparedStatement.setString(4, collectionDetails);
+			connection.executeUpdate(preparedStatement);
+
+			return newObjId;
+		} catch (SQLException ex) {
+			throw new TskCoreException(String.format("Error adding image to database"), ex);
+		} finally {
+			releaseSingleUserCaseWriteLock();
+		}
+	}
+	
+	/**
+	 * Add an image name to the database.
+	 * For use with the JNI callbacks associated with the add image process.
+	 * 
+	 * @param objId    The object id of the image.
+	 * @param name     The file name for the image
+	 * @param sequence The sequence number of this file.
+	 * @param transaction The open transaction.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	void addImageNameJNI(long objId, String name, long sequence,
+			CaseDbTransaction transaction) throws TskCoreException {
+		acquireSingleUserCaseWriteLock();
+		try {
+			CaseDbConnection connection = transaction.getConnection();
+			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_NAME);
+			preparedStatement.clearParameters();
+			preparedStatement.setLong(1, objId);
+			preparedStatement.setString(2, name);
+			preparedStatement.setLong(3, sequence);
+			connection.executeUpdate(preparedStatement);
+		} catch (SQLException ex) {
+			throw new TskCoreException(String.format("Error adding image name %s to image with object ID %d", name, objId), ex);
+		} finally {
+			releaseSingleUserCaseWriteLock();
+		}
+	}
+	
+	/**
+	 * Looks up a parent file object ID.
+	 * The calling thread is expected to have a case read lock.
+	 * For use with the JNI callbacks associated with the add image process.
+	 * 
+	 * @param metaAddr  The metadata address.
+	 * @param fsObjId   The file system object ID.
+	 * @param path      The file path.
+	 * @param name      The file name.
+	 * @param transaction The open transaction.
+	 * 
+	 * @return The object ID if found, -1 otherwise.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	long findParentObjIdJNI(long metaAddr, long fsObjId, String path, String name, CaseDbTransaction transaction) throws TskCoreException {
+		ResultSet resultSet = null;
+		try {
+			CaseDbConnection connection = transaction.getConnection();
+			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_OBJ_ID_BY_META_ADDR_AND_PATH);
+			preparedStatement.clearParameters();
+			preparedStatement.setLong(1, metaAddr);
+			preparedStatement.setLong(2, fsObjId);
+			preparedStatement.setString(3, path);
+			preparedStatement.setString(4, name);
+			resultSet = connection.executeQuery(preparedStatement);
+			if (resultSet.next()) {
+				return resultSet.getLong("obj_id");
+			} else {
+				throw new TskCoreException(String.format("Error looking up parent meta addr %d", metaAddr));
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException(String.format("Error looking up parent meta addr %d", metaAddr), ex);
+		} finally {
+			closeResultSet(resultSet);
+		}
+	}
+	
+	/**
+	 * Add a file system file to the database.
+	 * For use with the JNI callbacks associated with the add image process.
+	 * 
+	 * @param parentObjId     The parent of the file.
+	 * @param fsObjId         The object ID of the file system.
+	 * @param dataSourceObjId The data source object ID.
+	 * @param fsType    The type.
+	 * @param attrType  The type attribute given to the file by the file system.
+	 * @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 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
+	 *                     the metadata structure of the file system.
+	 * @param dirFlags  The allocated status of the file, usually as
+	 *                     reported in the name structure of the file system.
+	 * @param metaFlags The allocated status of the file, usually as
+	 *                     reported in the metadata structure of the file system.
+	 * @param size      The file size.
+	 * @param crtime    The created time.
+	 * @param ctime     The last changed time
+	 * @param atime     The last accessed time.
+	 * @param mtime     The last modified time.
+	 * @param meta_mode The modes for the file.
+	 * @param gid       The group identifier.
+	 * @param uid       The user identifier.
+	 * @param md5       The MD5 hash.
+	 * @param known     The file known status.
+	 * @param escaped_path The escaped path to the file.
+	 * @param extension    The file extension.
+	 * @param hasLayout    True if this is a layout file, false otherwise.
+	 * @param transaction  The open transaction.
+	 * 
+	 * @return The object ID of the new file system
+	 * 
+	 * @throws TskCoreException 
+	 */
+	long addFileJNI(long parentObjId, 
+			Long fsObjId, long dataSourceObjId,
+			int fsType,
+			Integer attrType, Integer 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,
+			Integer meta_mode, Integer gid, Integer uid,
+			String md5, TskData.FileKnown known,
+			String escaped_path, String extension, 
+			boolean hasLayout, CaseDbTransaction transaction) throws TskCoreException {
+
+		Statement queryStatement = null;
+		try {
+			acquireSingleUserCaseWriteLock();
+			CaseDbConnection connection = transaction.getConnection();
+
+			// Insert a row for the local/logical file into the tsk_objects table.
+			// INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
+			long objectId = addObject(parentObjId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
+
+			// INSERT INTO tsk_files (fs_obj_id, obj_id, data_source_obj_id, type, attr_type, attr_id, name, meta_addr, meta_seq, 
+			//                        dir_type, meta_type, dir_flags, meta_flags, size, crtime, ctime, atime, mtime, 
+			//                        mode, gid, uid, md5, known, parent_path, extension)
+			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE_SYSTEM_FILE_All_FIELDS);
+			statement.clearParameters();
+			if (fsObjId != null) {
+				statement.setLong(1, fsObjId);			    // fs_obj_id
+			} else {
+				statement.setNull(1, java.sql.Types.BIGINT);
+			}
+			statement.setLong(2, objectId);					// obj_id 
+			statement.setLong(3, dataSourceObjId);			// data_source_obj_id 
+			statement.setShort(4, (short)fsType);	        // type
+			if (attrType != null) {
+				statement.setShort(5, attrType.shortValue());  // attr_type
+			} else {
+				statement.setNull(5, java.sql.Types.SMALLINT);
+			}
+			if (attrId != null) {
+				statement.setInt(6, attrId);				// attr_id
+			} else {
+				statement.setNull(6, java.sql.Types.INTEGER);
+			}
+			statement.setString(7, name);					// name
+			if (metaAddr != null) {
+				statement.setLong(8, metaAddr);				// meta_addr
+			} else {
+				statement.setNull(8, java.sql.Types.BIGINT);
+			}
+			if (metaSeq != null) {
+				statement.setInt(9, metaSeq.intValue());	// meta_seq
+			} else {
+				statement.setNull(9, java.sql.Types.INTEGER);
+			}
+			statement.setShort(10, (short)dirType);			// dir_type
+			statement.setShort(11, (short)metaType);		// meta_type
+			statement.setShort(12, (short)dirFlags);		// dir_flags
+			statement.setShort(13, (short)metaFlags);		// meta_flags
+			statement.setLong(14, size < 0 ? 0 : size);     // size
+			if (crtime != null) {
+				statement.setLong(15, crtime);              // crtime
+			} else {
+				statement.setNull(15, java.sql.Types.BIGINT);
+			}
+			if (ctime != null) {
+				statement.setLong(16, ctime);               // ctime
+			} else {
+				statement.setNull(16, java.sql.Types.BIGINT);
+			}
+			if (atime != null) {
+				statement.setLong(17, atime);               // atime
+			} else {
+				statement.setNull(17, java.sql.Types.BIGINT);
+			}
+			if (mtime != null) {
+				statement.setLong(18, mtime);               // mtime
+			} else {
+				statement.setNull(18, java.sql.Types.BIGINT);
+			}
+			if (meta_mode != null) {
+				statement.setLong(19, meta_mode);           // mode
+			} else {
+				statement.setNull(19, java.sql.Types.BIGINT);
+			}
+			if (gid != null) {
+				statement.setLong(20, gid);                 // gid
+			} else {
+				statement.setNull(20, java.sql.Types.BIGINT);
+			}
+			if (uid != null) {
+				statement.setLong(21, uid);                 // uid
+			} else {
+				statement.setNull(21, java.sql.Types.BIGINT);
+			}
+			statement.setString(22, md5);                   // md5
+			statement.setInt(23, known.getFileKnownValue());// known
+			statement.setString(24, escaped_path);          // parent_path
+			statement.setString(25, extension);             // extension
+			if (hasLayout) {
+				statement.setInt(26, 1);                    // has_layout
+			} else {
+				statement.setNull(26, java.sql.Types.INTEGER);
+			}
+			connection.executeUpdate(statement);
+
+			// If this is not a slack file create the timeline events
+			if (! hasLayout
+					&& TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() != fsType
+					&& (!name.equals(".")) && (!name.equals(".."))) {
+				TimelineManager timelineManager = getTimelineManager();
+				DerivedFile derivedFile = new DerivedFile(this, objectId, dataSourceObjId, name, 
+						TSK_FS_NAME_TYPE_ENUM.valueOf((short)dirType),
+						TSK_FS_META_TYPE_ENUM.valueOf((short)metaType), 
+						TSK_FS_NAME_FLAG_ENUM.valueOf(dirFlags), 
+						(short)metaFlags,
+						size, ctime, crtime, atime, mtime, null, null, escaped_path, null, parentObjId, null, null, extension);
+
+				timelineManager.addEventsForNewFileQuiet(derivedFile, connection);	
+			}
+			
+			return objectId;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Failed to add file system file", ex);
+		} finally {
+			closeStatement(queryStatement);
+			releaseSingleUserCaseWriteLock();
+		}
+	}
+	
+	/**
+	 * Add a layout file range to the database.
+	 * For use with the JNI callbacks associated with the add image process.
+	 * 
+	 * @param objId     Object ID of the layout file.
+	 * @param byteStart Start byte.
+	 * @param byteLen   Length in bytes.
+	 * @param seq       Sequence number of this range.
+	 * @param transaction The open transaction.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	void addLayoutFileRangeJNI(long objId, long byteStart, long byteLen, 
+			long seq, CaseDbTransaction transaction) throws TskCoreException {
+		try {
+			acquireSingleUserCaseWriteLock();
+			CaseDbConnection connection = transaction.getConnection();
+			
+			PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LAYOUT_FILE);
+			prepStmt.clearParameters();
+			prepStmt.setLong(1, objId); 
+			prepStmt.setLong(2, byteStart);
+			prepStmt.setLong(3, byteLen);
+			prepStmt.setLong(4, seq);
+			connection.executeUpdate(prepStmt);
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error adding layout range to file with obj ID " + objId, ex);
+		} finally {
+			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
@@ -11061,6 +11508,8 @@ private enum PREPARED_STATEMENT {
 				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), //NON-NLS
 		INSERT_FILE_SYSTEM_FILE("INSERT INTO tsk_files(obj_id, fs_obj_id, data_source_obj_id, attr_type, attr_id, name, meta_addr, meta_seq, type, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, parent_path, extension)"
 				+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), // NON-NLS
+		INSERT_FILE_SYSTEM_FILE_All_FIELDS("INSERT INTO tsk_files (fs_obj_id, obj_id, data_source_obj_id, type, attr_type, attr_id, name, meta_addr, meta_seq, dir_type, meta_type, dir_flags, meta_flags, size, crtime, ctime, atime, mtime, mode, gid, uid, md5, known, parent_path, extension, has_layout)"
+				+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), // NON-NLS
 		UPDATE_DERIVED_FILE("UPDATE tsk_files SET type = ?, dir_type = ?, meta_type = ?, dir_flags = ?,  meta_flags = ?, size= ?, ctime= ?, crtime= ?, atime= ?, mtime= ?, mime_type = ?  "
 				+ "WHERE obj_id = ?"), //NON-NLS
 		INSERT_LAYOUT_FILE("INSERT INTO tsk_file_layout (obj_id, byte_start, byte_len, sequence) " //NON-NLS
@@ -11173,12 +11622,14 @@ private enum PREPARED_STATEMENT {
 		INSERT_IMAGE_INFO("INSERT INTO tsk_image_info (obj_id, type, ssize, tzone, size, md5, sha1, sha256, display_name)"
 				+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
 		INSERT_DATA_SOURCE_INFO("INSERT INTO data_source_info (obj_id, device_id, time_zone) VALUES (?, ?, ?)"),
+		INSERT_DATA_SOURCE_INFO_WITH_ACQ_DETAIL("INSERT INTO data_source_info (obj_id, device_id, time_zone, acquisition_details) VALUES (?, ?, ?, ?)"),
 		INSERT_VS_INFO("INSERT INTO tsk_vs_info (obj_id, vs_type, img_offset, block_size) VALUES (?, ?, ?, ?)"),
 		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 (?, ?, ?, ?, ?, ?, ?, ?, ?)");
+				+ "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 = ?");
 
 		private final String sql;
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
index 030284cc1d9a6612697c1c61a63067ad9ba075e2..c1d398602ede2354f42559fb0db1feb5225ec661 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -411,13 +411,17 @@ void free() throws TskCoreException {
 		 *                          case database.
 		 */
 		long addImageInfo(long deviceObjId, List<String> imageFilePaths, String timeZone, SleuthkitCase skCase) throws TskCoreException {
+			JniDbHelper dbHelper = new JniDbHelper(skCase);
 			try {
-				long tskAutoDbPointer = initializeAddImgNat(caseDbPointer, timezoneLongToShort(timeZone), false, false, false);
-				runOpenAndAddImgNat(tskAutoDbPointer, UUID.randomUUID().toString(), imageFilePaths.toArray(new String[0]), imageFilePaths.size(), timeZone);
-				long id = commitAddImgNat(tskAutoDbPointer);
+				dbHelper.beginTransaction();
+				long tskAutoDbPointer = initializeAddImgNat(caseDbPointer, 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();
 				skCase.addDataSourceToHasChildrenMap();
 				return id;
 			} catch (TskDataException ex) {
+				dbHelper.revertTransaction();
 				throw new TskCoreException("Error adding image to case database", ex);
 			}
 		}
@@ -455,6 +459,7 @@ public class AddImageProcess {
 			private volatile long tskAutoDbPointer;
 			private boolean isCanceled;
 			private final SleuthkitCase skCase;
+			private final JniDbHelper dbHelper;
 
 			/**
 			 * Constructs an object that encapsulates a multi-step process to
@@ -477,6 +482,7 @@ private AddImageProcess(String timeZone, boolean addUnallocSpace, boolean skipFa
 				tskAutoDbPointer = 0;
 				this.isCanceled = false;
 				this.skCase = skCase;
+				this.dbHelper = new JniDbHelper(skCase);
 			}
 
 			/**
@@ -501,14 +507,14 @@ public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws
 				getTSKReadLock();
 				try {
 					long imageHandle = 0;
-
 					synchronized (this) {
 						if (0 != tskAutoDbPointer) {
 							throw new TskCoreException("Add image process already started");
 						}
 						if (!isCanceled) { //with isCanceled being guarded by this it will have the same value everywhere in this synchronized block
 							imageHandle = openImage(imageFilePaths, sectorSize, false, caseDbPointer);
-							tskAutoDbPointer = initAddImgNat(caseDbPointer, timezoneLongToShort(timeZone), addUnallocSpace, skipFatFsOrphans);
+							dbHelper.beginTransaction();
+							tskAutoDbPointer = initAddImgNat(caseDbPointer, dbHelper, timezoneLongToShort(timeZone), addUnallocSpace, skipFatFsOrphans);
 						}
 						if (0 == tskAutoDbPointer) {
 							throw new TskCoreException("initAddImgNat returned a NULL TskAutoDb pointer");
@@ -556,9 +562,11 @@ public synchronized void revert() throws TskCoreException {
 					if (tskAutoDbPointer == 0) {
 						throw new TskCoreException("AddImgProcess::revert: AutoDB pointer is NULL");
 					}
-
-					revertAddImgNat(tskAutoDbPointer);
-					// the native code deleted the object
+					
+					dbHelper.revertTransaction();
+					
+					// Delete the object in the native code
+					finishAddImgNat(tskAutoDbPointer);
 					tskAutoDbPointer = 0;
 				} finally {
 					releaseTSKReadLock();
@@ -581,12 +589,13 @@ public synchronized long commit() throws TskCoreException {
 						throw new TskCoreException("AddImgProcess::commit: AutoDB pointer is NULL");
 					}
 
-					long id = commitAddImgNat(tskAutoDbPointer);
+					dbHelper.commitTransaction();
 
-					skCase.addDataSourceToHasChildrenMap();
-
-					// the native code deleted the object
+					// Get the image ID and delete the object in the native code
+					long id = finishAddImgNat(tskAutoDbPointer);
 					tskAutoDbPointer = 0;
+					
+					skCase.addDataSourceToHasChildrenMap();
 					return id;
 				} finally {
 					releaseTSKReadLock();
@@ -1950,9 +1959,9 @@ public static long openFile(long fsHandle, long fileId, TSK_FS_ATTR_TYPE_ENUM at
 
 	private static native HashHitInfo hashDbLookupVerbose(String hash, int dbHandle) throws TskCoreException;
 
-	private static native long initAddImgNat(long db, String timezone, boolean addUnallocSpace, boolean skipFatFsOrphans) throws TskCoreException;
+	private static native long initAddImgNat(long db, JniDbHelper dbHelperObj, String timezone, boolean addUnallocSpace, boolean skipFatFsOrphans) throws TskCoreException;
 
-	private static native long initializeAddImgNat(long db, String timezone, boolean addFileSystems, boolean addUnallocSpace, boolean skipFatFsOrphans) throws TskCoreException;
+	private static native long initializeAddImgNat(long db, JniDbHelper dbHelperObj, String timezone, boolean addFileSystems, boolean addUnallocSpace, boolean skipFatFsOrphans) throws TskCoreException;
 
 	private static native void runOpenAndAddImgNat(long process, String deviceId, String[] imgPath, int splits, String timezone) throws TskCoreException, TskDataException;
 
@@ -1960,9 +1969,7 @@ public static long openFile(long fsHandle, long fileId, TSK_FS_ATTR_TYPE_ENUM at
 
 	private static native void stopAddImgNat(long process) throws TskCoreException;
 
-	private static native void revertAddImgNat(long process) throws TskCoreException;
-
-	private static native long commitAddImgNat(long process) throws TskCoreException;
+	private static native long finishAddImgNat(long process) throws TskCoreException;
 
 	private static native long openImgNat(String[] imgPath, int splits, int sSize) throws TskCoreException;
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java
index 5ddc48de94519d2271f6140e080ff83eeb2e81c9..296b548373a99bb50606118a778c6f5f4d949754 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java
@@ -487,6 +487,29 @@ private long addEventDescription(long dataSourceObjId, long fileObjId, Long arti
 	}
 
 	Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnection connection) throws TskCoreException {
+		Set<TimelineEvent> events = addEventsForNewFileQuiet(file, connection);
+		events.stream()
+				.map(TimelineEventAddedEvent::new)
+				.forEach(caseDB::fireTSKEvent);
+
+		return events;
+	}
+	
+	/**
+	 * Adds timeline events for the new file to the database.
+	 * Does not fire TSKEvents for each addition. This method should only be used if an 
+	 * update event will be sent later. For example, a data source processor may
+	 * send out a single event that a data source has been added rather than an event
+	 * for each timeline event.
+	 * 
+	 * @param file        The new file
+	 * @param connection  Database connection to use
+	 * 
+	 * @return Set of new events
+	 * 
+	 * @throws TskCoreException 
+	 */
+	Set<TimelineEvent> addEventsForNewFileQuiet(AbstractFile file, CaseDbConnection connection) throws TskCoreException {
 		//gather time stamps into map
 		Map<TimelineEventType, Long> timeMap = ImmutableMap.of(TimelineEventType.FILE_CREATED, file.getCrtime(),
 				TimelineEventType.FILE_ACCESSED, file.getAtime(),
@@ -532,9 +555,6 @@ Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnectio
 		} finally {
 			caseDB.releaseSingleUserCaseWriteLock();
 		}
-		events.stream()
-				.map(TimelineEventAddedEvent::new)
-				.forEach(caseDB::fireTSKEvent);
 
 		return events;
 	}
diff --git a/tsk/auto/tsk_db.h b/tsk/auto/tsk_db.h
index a95aaecc5fa95db67f4d3cb6146d7ac9d4ea51e8..edb300dcee781cb2cb7462f1ca842f3e71507dfc 100755
--- a/tsk/auto/tsk_db.h
+++ b/tsk/auto/tsk_db.h
@@ -228,7 +228,8 @@ class TskDb {
 
 	  @param name A file name
 	  @param extension The file name extension will be extracted to extension.
-	  */void extractExtension(char *name, char *extension ) {
+	  */
+      void extractExtension(char *name, char *extension ) {
 		   char *ext = strrchr(name, '.');
 
 		   //if ext is not null and is not the entire filename...
diff --git a/win32/tsk_jni/tsk_jni.vcxproj b/win32/tsk_jni/tsk_jni.vcxproj
index 182be991cda54a1634ec7c0a28a028b42e6cc6c7..c5ecd0a12553a1d6b86a187d49512d0859a04643 100755
--- a/win32/tsk_jni/tsk_jni.vcxproj
+++ b/win32/tsk_jni/tsk_jni.vcxproj
@@ -578,9 +578,11 @@
     </PostBuildEvent>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClCompile Include="..\..\bindings\java\jni\auto_db_java.cpp" />
     <ClCompile Include="..\..\bindings\java\jni\dataModel_SleuthkitJNI.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="..\..\bindings\java\jni\auto_db_java.h" />
     <ClInclude Include="..\..\bindings\java\jni\dataModel_SleuthkitJNI.h" />
   </ItemGroup>
   <ItemGroup>
diff --git a/win32/tsk_jni/tsk_jni.vcxproj.filters b/win32/tsk_jni/tsk_jni.vcxproj.filters
index 3a1bcb722218d3405eb6cf8a73c15364285c6df1..ce3a1c769220e3e9bc605b36cdd46c98fc70f8cf 100755
--- a/win32/tsk_jni/tsk_jni.vcxproj.filters
+++ b/win32/tsk_jni/tsk_jni.vcxproj.filters
@@ -14,10 +14,16 @@
     <ClCompile Include="..\..\bindings\java\jni\dataModel_SleuthkitJNI.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\bindings\java\jni\auto_db_java.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\bindings\java\jni\dataModel_SleuthkitJNI.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\bindings\java\jni\auto_db_java.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>
\ No newline at end of file