diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp
index 276277dbf90f25479a9c286aad84d84f063613bb..a20fd04b4a53c1d9938093babd2ad1e0be6b928a 100644
--- a/bindings/java/jni/auto_db_java.cpp
+++ b/bindings/java/jni/auto_db_java.cpp
@@ -79,16 +79,11 @@ TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) {
     }
     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");
+    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;[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;
@@ -184,7 +179,8 @@ TskAutoDbJava::getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info) {
 */
 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 string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails,
+    char** img_ptrs, int num_imgs) {
 
     const char *tz_cstr = timezone.c_str();
     jstring tzj = m_jniEnv->NewStringUTF(tz_cstr);
@@ -204,8 +200,18 @@ TskAutoDbJava::addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const st
     const char *coll_cstr = collectionDetails.c_str();
     jstring collj = m_jniEnv->NewStringUTF(coll_cstr);
 
+    jobjectArray imgNamesj = (jobjectArray)m_jniEnv->NewObjectArray(
+        num_imgs,
+        m_jniEnv->FindClass("java/lang/String"),
+        m_jniEnv->NewStringUTF(""));
+
+    for (int i = 0; i < num_imgs; i++) {
+        m_jniEnv->SetObjectArrayElement(
+            imgNamesj, i, m_jniEnv->NewStringUTF(img_ptrs[i]));
+    }
+
     jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addImageMethodID,
-        type, ssize, tzj, size, md5j, sha1j, sha256j, devIdj, collj);
+        type, ssize, tzj, size, md5j, sha1j, sha256j, devIdj, collj, imgNamesj);
     objId = (int64_t)objIdj;
 
     if (objId < 0) {
@@ -216,29 +222,6 @@ TskAutoDbJava::addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const st
     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.
 *
@@ -1046,6 +1029,11 @@ TskAutoDbJava::openImage(const char* a_deviceId)
 uint8_t
 TskAutoDbJava::addImageDetails(const char* deviceId)
 {
+    // The image has already been added to the database
+    if (m_curImgId > 0) {
+        return 0;
+    }
+
    string md5 = "";
    string sha1 = "";
    string collectionDetails = "";
@@ -1070,13 +1058,6 @@ TskAutoDbJava::addImageDetails(const char* 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
@@ -1110,14 +1091,12 @@ TskAutoDbJava::addImageDetails(const char* deviceId)
     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;
-        }
+    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,
+        img_ptrs, m_img_info->num_img)) {
+        registerError();
+        return 1;
     }
 
 #ifdef TSK_WIN32
@@ -1530,6 +1509,15 @@ TskAutoDbJava::setTz(string tzone)
     m_curImgTZone = tzone;
 }
 
+/**
+ * Set the object ID for the data source
+ */
+void 
+TskAutoDbJava::setDatasourceObjId(int64_t img_id)
+{
+    m_curImgId = img_id;
+}
+
 TSK_RETVAL_ENUM
 TskAutoDbJava::processFile(TSK_FS_FILE * fs_file, const char *path)
 {
diff --git a/bindings/java/jni/auto_db_java.h b/bindings/java/jni/auto_db_java.h
index 3609d6a38c274f9829d13f403c21921387d9a92e..26e370d093b7229cad868d813969be22324930a5 100644
--- a/bindings/java/jni/auto_db_java.h
+++ b/bindings/java/jni/auto_db_java.h
@@ -44,6 +44,7 @@ class TskAutoDbJava :public TskAuto {
     virtual void closeImage();
     void close();
     virtual void setTz(string tzone);
+    virtual void setDatasourceObjId(int64_t img_id);
 
     virtual TSK_FILTER_ENUM filterVs(const TSK_VS_INFO * vs_info);
     virtual TSK_FILTER_ENUM filterVol(const TSK_VS_PART_INFO * vs_part);
@@ -206,7 +207,7 @@ class TskAutoDbJava :public TskAuto {
 
     // 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);
+        const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails, char** img_ptrs, int num_imgs);
     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);
diff --git a/bindings/java/jni/dataModel_SleuthkitJNI.cpp b/bindings/java/jni/dataModel_SleuthkitJNI.cpp
index 5452ccbeac183314b784bbc25a37ce681511a3af..f4da3b4b62a2c5b29289ca6962b732af7a9bf1eb 100644
--- a/bindings/java/jni/dataModel_SleuthkitJNI.cpp
+++ b/bindings/java/jni/dataModel_SleuthkitJNI.cpp
@@ -15,6 +15,10 @@
 #include "tsk/img/img_writer.h"
 #include "tsk/img/raw.h"
 #include "auto_db_java.h"
+#if HAVE_LIBEWF
+#include "tsk/img/ewf.h"
+#include "tsk/img/tsk_img_i.h"
+#endif
 #include "jni.h"
 #include "dataModel_SleuthkitJNI.h"
 #include <locale.h>
@@ -905,7 +909,7 @@ JNIEXPORT void JNICALL
     TskAutoDbJava *tskAuto = ((TskAutoDbJava *) process);
     if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
         setThrowTskCoreError(env, 
-            "runAddImgNat: Invalid TskAutoDbJava object passed in");
+            "runOpenAndAddImgNat: Invalid TskAutoDbJava object passed in");
         return;
     }
 
@@ -914,7 +918,7 @@ JNIEXPORT void JNICALL
     if (NULL != deviceId) {    
         device_id = (const char *) env->GetStringUTFChars(deviceId, &isCopy);
         if (NULL == device_id) {
-            setThrowTskCoreError(env, "runAddImgNat: Can't convert data source id string");
+            setThrowTskCoreError(env, "runOpenAndAddImgNat: Can't convert data source id string");
             return;
         }
     }
@@ -933,7 +937,7 @@ JNIEXPORT void JNICALL
             GetStringUTFChars(jsPath, &isCopy);
         if (imagepaths8[i] == NULL) {
             setThrowTskCoreError(env,
-                "runAddImgNat: Can't convert path strings.");
+                "runOpenAndAddImgNat: Can't convert path strings.");
             // @@@ should cleanup here paths that have been converted in imagepaths8[i]
             return;
         }
@@ -997,11 +1001,12 @@ JNIEXPORT void JNICALL
 * @param process the add-image process created by initAddImgNat
 * @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 a_img_info image info object
+* @param img_id The object ID of the image in the database
 * @param timeZone the timezone the image is from
 */
 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) {
+    jclass obj, jlong process, jstring deviceId, jlong a_img_info, jlong img_id, jstring timeZone, jstring imageWriterPathJ) {
     
     TskAutoDbJava *tskAuto = ((TskAutoDbJava *)process);
     if (!tskAuto || tskAuto->m_tag != TSK_AUTO_TAG) {
@@ -1020,6 +1025,9 @@ Java_org_sleuthkit_datamodel_SleuthkitJNI_runAddImgNat(JNIEnv * env,
         }
     }
 
+    // Set the data source object ID
+    tskAuto->setDatasourceObjId(img_id);
+
     // Set the time zone.
     if (env->GetStringLength(timeZone) > 0) {
         const char *time_zone = env->GetStringUTFChars(timeZone, &isCopy);
@@ -1125,7 +1133,6 @@ Java_org_sleuthkit_datamodel_SleuthkitJNI_finishAddImgNat(JNIEnv * env,
 }
 
 
-
 /*
  * Open an image pointer for the given image.
  * @return the created TSK_IMG_INFO pointer
@@ -1142,23 +1149,23 @@ JNIEXPORT jlong JNICALL
     jboolean isCopy;
 
     // get pointers to each of the file names
-    char **imagepaths8 = (char **) tsk_malloc(num_imgs * sizeof(char *));
+    char **imagepaths8 = (char **)tsk_malloc(num_imgs * sizeof(char *));
     if (imagepaths8 == NULL) {
         setThrowTskCoreError(env);
         return 0;
     }
     for (int i = 0; i < num_imgs; i++) {
         imagepaths8[i] =
-            (char *) env->
-            GetStringUTFChars((jstring) env->GetObjectArrayElement(paths,
+            (char *)env->
+            GetStringUTFChars((jstring)env->GetObjectArrayElement(paths,
                 i), &isCopy);
         // @@@ Error check
     }
 
     // open the image
     img_info =
-        tsk_img_open_utf8((int) num_imgs, imagepaths8, TSK_IMG_TYPE_DETECT,
-        sector_size);
+        tsk_img_open_utf8((int)num_imgs, imagepaths8, TSK_IMG_TYPE_DETECT,
+            sector_size);
     if (img_info == NULL) {
         setThrowTskCoreError(env, tsk_error_get());
     }
@@ -1167,14 +1174,186 @@ JNIEXPORT jlong JNICALL
     for (int i = 0; i < num_imgs; i++) {
         env->
             ReleaseStringUTFChars((jstring)
-            env->GetObjectArrayElement(paths, i), imagepaths8[i]);
+                env->GetObjectArrayElement(paths, i), imagepaths8[i]);
     }
     free(imagepaths8);
 
     return (jlong) img_info;
 }
 
+/*
+ * Get the full list of paths associated with an image.
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getPathsForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+
+    char **img_ptrs;
+#ifdef TSK_WIN32
+    // convert image paths to UTF-8
+    img_ptrs = (char **)tsk_malloc(img_info->num_img * sizeof(char *));
+    if (img_ptrs == NULL) {
+        return (jobjectArray)env->NewObjectArray(0, env->FindClass("java/lang/String"), env->NewStringUTF(""));
+    }
+
+    for (int i = 0; i < img_info->num_img; i++) {
+        char * img2 = (char*)tsk_malloc(1024 * sizeof(char));
+        UTF8 *ptr8;
+        UTF16 *ptr16;
+
+        ptr8 = (UTF8 *)img2;
+        ptr16 = (UTF16 *)img_info->images[i];
+
+        uint8_t retval =
+            tsk_UTF16toUTF8_lclorder((const UTF16 **)&ptr16, (UTF16 *)
+                & ptr16[TSTRLEN(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 (jobjectArray)env->NewObjectArray(0, env->FindClass("java/lang/String"), env->NewStringUTF(""));
+        }
+        img_ptrs[i] = img2;
+    }
+#else 
+    img_ptrs = img_info->images;
+#endif
+
+    jobjectArray path_list = (jobjectArray)env->NewObjectArray(img_info->num_img, env->FindClass("java/lang/String"), env->NewStringUTF(""));
+    for (int i = 0; i < img_info->num_img; i++) {
+        env->SetObjectArrayElement(path_list, i, env->NewStringUTF(img_ptrs[i]));
+    }
+
+    return path_list;
+}
+
+
+/*
+ * Get the size of an image.
+ */
+JNIEXPORT jlong JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getSizeForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+
+    return img_info->size;
+}
+
+
+/*
+ * Get the type of an image.
+ */
+JNIEXPORT jlong JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getTypeForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+
+    return img_info->itype;
+}
+
+
+/*
+* Get the computed sector size of an image.
+*/
+JNIEXPORT jlong JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getSectorSizeForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+
+    return img_info->sector_size;
+}
+
+/*
+* Get the md5 hash of an image.
+*/
+JNIEXPORT jstring JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getMD5HashForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+    // env->NewStringUTF(img_ptrs[i])
+#if HAVE_LIBEWF 
+    if (img_info->itype == TSK_IMG_TYPE_EWF_EWF) {
+        IMG_EWF_INFO *ewf_info = (IMG_EWF_INFO *)img_info;
+        if (ewf_info->md5hash_isset) {
+            return env->NewStringUTF(ewf_info->md5hash);
+        }
+    }
+#endif
+    return env->NewStringUTF("");
+}
 
+/*
+* Get the sha1 hash of an image.
+*/
+JNIEXPORT jstring JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getSha1HashForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+    // env->NewStringUTF(img_ptrs[i])
+#if HAVE_LIBEWF 
+    if (img_info->itype == TSK_IMG_TYPE_EWF_EWF) {
+        IMG_EWF_INFO *ewf_info = (IMG_EWF_INFO *)img_info;
+        if (ewf_info->sha1hash_isset) {
+            return env->NewStringUTF(ewf_info->sha1hash);
+        }
+    }
+#endif
+    return env->NewStringUTF("");
+}
+
+/*
+* Get the collection details of an image.
+*/
+JNIEXPORT jstring JNICALL
+Java_org_sleuthkit_datamodel_SleuthkitJNI_getCollectionDetailsForImageNat(JNIEnv * env,
+    jclass obj, jlong a_img_info) {
+
+    TSK_IMG_INFO *img_info = castImgInfo(env, a_img_info);
+    if (img_info == 0) {
+        //exception already set
+        return 0;
+    }
+    // env->NewStringUTF(img_ptrs[i])
+#if HAVE_LIBEWF 
+    if (img_info->itype == TSK_IMG_TYPE_EWF_EWF) {
+        IMG_EWF_INFO *ewf_info = (IMG_EWF_INFO *)img_info;
+        ewf_get_details(ewf_info);
+    }
+#endif
+    return env->NewStringUTF("");
+}
 
 /*
  * Open the volume system at the given offset
diff --git a/bindings/java/jni/dataModel_SleuthkitJNI.h b/bindings/java/jni/dataModel_SleuthkitJNI.h
index ebd2b8fc39ef3a1ebe032e5bc153b00f24cf16bf..ed49532dc0106f329d0e134e5909ac8d41504036 100644
--- a/bindings/java/jni/dataModel_SleuthkitJNI.h
+++ b/bindings/java/jni/dataModel_SleuthkitJNI.h
@@ -194,10 +194,10 @@ JNIEXPORT void JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_runOpenAndAddIm
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
  * Method:    runAddImgNat
- * Signature: (JLjava/lang/String;JLjava/lang/String;Ljava/lang/String;)V
+ * Signature: (JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;)V
  */
 JNIEXPORT void JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_runAddImgNat
-  (JNIEnv *, jclass, jlong, jstring, jlong, jstring, jstring);
+  (JNIEnv *, jclass, jlong, jstring, jlong, jlong, jstring, jstring);
 
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
@@ -327,6 +327,62 @@ JNIEXPORT jint JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_readFileNat
 JNIEXPORT jint JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_saveFileMetaDataTextNat
   (JNIEnv *, jclass, jlong, jstring);
 
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getPathsForImageNat
+ * Signature: (J)[Ljava/lang/String;
+ */
+JNIEXPORT jobjectArray JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getPathsForImageNat
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getSizeForImageNat
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getSizeForImageNat
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getTypeForImageNat
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getTypeForImageNat
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getSectorSizeForImageNat
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getSectorSizeForImageNat
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getMD5HashForImageNat
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getMD5HashForImageNat
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getSha1HashForImageNat
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getSha1HashForImageNat
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     org_sleuthkit_datamodel_SleuthkitJNI
+ * Method:    getCollectionDetailsForImageNat
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_getCollectionDetailsForImageNat
+  (JNIEnv *, jclass, jlong);
+
 /*
  * Class:     org_sleuthkit_datamodel_SleuthkitJNI
  * Method:    closeImgNat
diff --git a/bindings/java/src/org/sleuthkit/datamodel/AddDataSourceCallbacks.java b/bindings/java/src/org/sleuthkit/datamodel/AddDataSourceCallbacks.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac4912ad33434d58ee146ba4ff1479730d5c47d0
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/AddDataSourceCallbacks.java
@@ -0,0 +1,33 @@
+/*
+ * SleuthKit Java Bindings
+ *
+ * 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.List;
+
+/**
+ * Provides callbacks at key points during the process of adding a data source to a case database.
+ */
+public interface AddDataSourceCallbacks {
+    /**
+     * Call to add a set of file object IDs that have been added to the database.
+     * 
+     * @param fileObjectIds List of file object IDs.
+     */
+    void onFilesAdded(List<Long> fileObjectIds);
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/DefaultAddDataSourceCallbacks.java b/bindings/java/src/org/sleuthkit/datamodel/DefaultAddDataSourceCallbacks.java
new file mode 100644
index 0000000000000000000000000000000000000000..db378ae750103d5f4b08f0f389a6fef766cd8f62
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/DefaultAddDataSourceCallbacks.java
@@ -0,0 +1,31 @@
+/*
+ * SleuthKit Java Bindings
+ *
+ * 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.List;
+
+/**
+ * Do-nothing version of AddDataSourceCallbacks
+ */
+public class DefaultAddDataSourceCallbacks implements AddDataSourceCallbacks {
+    @Override
+    public void onFilesAdded(List<Long> fileObjectIds) {
+        // Do nothing
+    }	
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Examples/Sample.java b/bindings/java/src/org/sleuthkit/datamodel/Examples/Sample.java
index 01c2504980304f61e174835fccd4bee43816121e..c6b3031b60a05f378b21fff9d7af28213fb20ed3 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/Examples/Sample.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Examples/Sample.java
@@ -50,7 +50,6 @@ public static void run(String imagePath) {
 			} catch (TskDataException ex) {
 				Logger.getLogger(Sample.class.getName()).log(Level.SEVERE, null, ex);
 			}
-			process.commit();
 
 			// print out all the images found, and their children
 			List<Image> images = sk.getImages();
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Image.java b/bindings/java/src/org/sleuthkit/datamodel/Image.java
index 161e51f3adb132a14d83394f521c0349293ac93b..a465af5975966e2f9cc12870732f1af7f1a3328c 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Image.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Image.java
@@ -129,6 +129,10 @@ public synchronized long getImageHandle() throws TskCoreException {
 
 		return imageHandle;
 	}
+	
+	synchronized void setImageHandle(long imageHandle) {
+		this.imageHandle = imageHandle;
+	}
 
 	@Override
 	public Content getDataSource() {
diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
index 6596b0dd5caabc9a243a2af105530143942731de..0b753b6246c96cda74b4f8a196a7094cb36a89ef 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
@@ -22,8 +22,10 @@
 import java.util.List;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Queue;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
@@ -33,6 +35,9 @@
  * 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.
+ * 
+ * Note that this code should only be used for the add image process, and not
+ * to add additional files afterward.
  */
 class JniDbHelper {
     
@@ -40,17 +45,20 @@ class JniDbHelper {
     
     private final SleuthkitCase caseDb;
     private CaseDbTransaction trans = null;
+    private final AddDataSourceCallbacks addDataSourceCallbacks;
     
     private final Map<Long, Long> fsIdToRootDir = new HashMap<>();
     private final Map<Long, TskData.TSK_FS_TYPE_ENUM> fsIdToFsType = new HashMap<>();
     private final Map<ParentCacheKey, Long> parentDirCache = new HashMap<>();
     
     private static final long BATCH_FILE_THRESHOLD = 500;
-    private final List<FileInfo> batchedFiles = new ArrayList<>();
-    private final List<LayoutRangeInfo> batchedLayoutRanges = new ArrayList<>();
+    private final Queue<FileInfo> batchedFiles = new LinkedList<>();
+    private final Queue<LayoutRangeInfo> batchedLayoutRanges = new LinkedList<>();
+    private final List<Long> layoutFileIds = new ArrayList<>();
     
-    JniDbHelper(SleuthkitCase caseDb) {
+    JniDbHelper(SleuthkitCase caseDb, AddDataSourceCallbacks addDataSourceCallbacks) {
         this.caseDb = caseDb;
+        this.addDataSourceCallbacks = addDataSourceCallbacks;
         trans = null;
     }
     
@@ -93,11 +101,14 @@ private void revertTransaction() {
     void finish() {
         addBatchedFilesToDb();
         addBatchedLayoutRangesToDb();
+        processLayoutFiles();
     }
     
     /**
      * Add a new image to the database.
      * Intended to be called from the native code during the add image process.
+	 * Will not be called if the image was added to the database prior to starting
+	 * the add image process.
      * 
      * @param type        Type of image.
      * @param ssize       Sector size.
@@ -113,41 +124,20 @@ void finish() {
      */
     long addImageInfo(int type, long ssize, String timezone, 
             long size, String md5, String sha1, String sha256, String deviceId, 
-            String collectionDetails) {    
+            String collectionDetails, String[] paths) {    
         try {
             beginTransaction();
             long objId = caseDb.addImageJNI(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), ssize, size,
                     timezone, md5, sha1, sha256, deviceId, collectionDetails, trans);
+            for (int i = 0;i < paths.length;i++) {
+                caseDb.addImageNameJNI(objId, paths[i], i, trans);
+            }
             commitTransaction();
             return objId;
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding image to the database", ex);
             revertTransaction();
             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 {
-            beginTransaction();
-            caseDb.addImageNameJNI(objId, name, sequence, trans);
-            commitTransaction();
-            return 0;
-        } catch (TskCoreException ex) {
-            logger.log(Level.SEVERE, "Error adding image name to the database - image obj ID: " + objId + ", image name: " + name
-                    + ", sequence: " + sequence, ex);
-            revertTransaction();
-            return -1;
         }
     }
     
@@ -338,9 +328,11 @@ long addFile(long parentObjId,
      * @return 0 if successful, -1 if not
      */
     private long addBatchedFilesToDb() {
+        List<Long> newObjIds = new ArrayList<>();
         try {
             beginTransaction();
-            for (FileInfo fileInfo : batchedFiles) {
+            FileInfo fileInfo;
+            while ((fileInfo = batchedFiles.poll()) != null) {
                 long computedParentObjId = fileInfo.parentObjId;
                 try {
                     // If we weren't given the parent object ID, look it up
@@ -360,6 +352,7 @@ private long addBatchedFilesToDb() {
                         null, TskData.FileKnown.UNKNOWN,
                         fileInfo.escaped_path, fileInfo.extension, 
                         false, trans);
+                    newObjIds.add(objId);
 
                     // If we're adding the root directory for the file system, cache it
                     if (fileInfo.parentObjId == fileInfo.fsObjId) {
@@ -379,46 +372,50 @@ private long addBatchedFilesToDb() {
                 } catch (TskCoreException ex) {
                     logger.log(Level.SEVERE, "Error adding file to the database - parent object ID: " + computedParentObjId
                             + ", file system object ID: " + fileInfo.fsObjId + ", name: " + fileInfo.name, ex);
-                    revertTransaction();
-                    return -1;
                 }
             }
             commitTransaction();
+            try {
+                addDataSourceCallbacks.onFilesAdded(newObjIds);
+			} catch (Exception ex) {
+                // Exception firewall to prevent unexpected return to the native code
+                logger.log(Level.SEVERE, "Unexpected error from files added callback", ex);
+            }
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding batched files to database", ex);
             revertTransaction();
             return -1;
         }
-        batchedFiles.clear();
         return 0;
     }
-	
-	/**
-	 * Look up the parent object ID for a file using the cache or the database.
-	 * 
-	 * @param fileInfo The file to find the parent of
-	 * 
-	 * @return Parent object ID
-	 * 
-	 * @throws TskCoreException 
-	 */
-	private long getParentObjId(FileInfo fileInfo) throws TskCoreException {
-		// Remove the final slash from the path unless we're in the root folder
-		String parentPath = fileInfo.escaped_path;
-		if(parentPath.endsWith("/") && ! parentPath.equals("/")) {
-			parentPath =  parentPath.substring(0, parentPath.lastIndexOf('/'));
-		}
+    
+    /**
+     * Look up the parent object ID for a file using the cache or the database.
+     * 
+     * @param fileInfo The file to find the parent of
+     * 
+     * @return Parent object ID
+     * 
+     * @throws TskCoreException 
+     */
+    private long getParentObjId(FileInfo fileInfo) throws TskCoreException {
+        // Remove the final slash from the path unless we're in the root folder
+        String parentPath = fileInfo.escaped_path;
+        if(parentPath.endsWith("/") && ! parentPath.equals("/")) {
+            parentPath =  parentPath.substring(0, parentPath.lastIndexOf('/'));
+        }
 
-		// Look up the parent
-		ParentCacheKey key = new ParentCacheKey(fileInfo.fsObjId, fileInfo.parMetaAddr, fileInfo.parSeq, parentPath);
-		if (parentDirCache.containsKey(key)) {
-			return parentDirCache.get(key);
-		} else {
-			// The parent wasn't found in the cache so do a database query
-			java.io.File parentAsFile = new java.io.File(parentPath);
-			return caseDb.findParentObjIdJNI(fileInfo.parMetaAddr, fileInfo.fsObjId, parentAsFile.getPath(), parentAsFile.getName(), trans);
-		}
-	}
+        // Look up the parent
+        ParentCacheKey key = new ParentCacheKey(fileInfo.fsObjId, fileInfo.parMetaAddr, fileInfo.parSeq, parentPath);
+        if (parentDirCache.containsKey(key)) {
+            return parentDirCache.get(key);
+        } else {
+            // There's no reason to do a database query since every folder added is being
+            // stored in the cache.
+            throw new TskCoreException("Parent not found in cache (fsObjId: " +fileInfo.fsObjId + ", parMetaAddr: " + fileInfo.parMetaAddr
+                + ", parSeq: " + fileInfo.parSeq + ", parentPath: " + parentPath + ")");
+        }
+    }
     
     /**
      * Add a layout file to the database. 
@@ -462,6 +459,10 @@ long addLayoutFile(long parentObjId,
                 null, null, 
                 true, trans);
             commitTransaction();
+
+            // Store the layout file ID for later processing
+            layoutFileIds.add(objId);
+
             return objId;
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding layout file to the database - parent object ID: " + parentObjId
@@ -499,19 +500,16 @@ long addLayoutFileRange(long objId, long byteStart, long byteLen, long seq) {
     private long addBatchedLayoutRangesToDb() {
         try {
             beginTransaction();
-    
-            for (LayoutRangeInfo range : batchedLayoutRanges) {
+    		LayoutRangeInfo range;
+            while ((range = batchedLayoutRanges.poll()) != null) {
                 try {
                     caseDb.addLayoutFileRangeJNI(range.objId, range.byteStart, range.byteLen, range.seq, trans);
                 } catch (TskCoreException ex) {
                     logger.log(Level.SEVERE, "Error adding layout file range to the database - layout file ID: " + range.objId 
                         + ", byte start: " + range.byteStart + ", length: " + range.byteLen + ", seq: " + range.seq, ex);
-                    revertTransaction();
-                    return -1;
                 }
             }
             commitTransaction();
-            batchedLayoutRanges.clear();
             return 0;
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error adding batched files to database", ex);
@@ -519,6 +517,16 @@ private long addBatchedLayoutRangesToDb() {
             return -1;
         }
     }
+	
+    /**
+     * Send completed layout files on for further processing.
+     * Note that this must wait until we know all the ranges for each
+     * file have been added to the database. 
+     */
+    void processLayoutFiles() {
+        addDataSourceCallbacks.onFilesAdded(layoutFileIds);
+        layoutFileIds.clear();
+    }
     
     /**
      * Add a virtual directory to hold unallocated file system blocks.
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 69d98bd4400ac9a83b721616b506a1d96f34440f..cc6b85ba950d1df91ec5e650054ce038624835eb 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -5920,7 +5920,16 @@ public Image addImage(TskData.TSK_IMG_TYPE_ENUM type, long sectorSize, long size
 			connection.executeUpdate(preparedStatement);
 
 			// Create the new Image object
-			return new Image(this, newObjId, type.getValue(), deviceId, sectorSize, displayName,
+			String name = displayName;
+			if (name == null || name.isEmpty()) {
+				if (imagePaths.size() > 0) {
+					String path = imagePaths.get(0);
+					name = (new java.io.File(path)).getName();
+				} else {
+					name = "";
+				}
+			}			
+			return new Image(this, newObjId, type.getValue(), deviceId, sectorSize, name,
 					imagePaths.toArray(new String[imagePaths.size()]), timezone, md5, sha1, sha256, savedSize);
 		} catch (SQLException ex) {
 			if (!imagePaths.isEmpty()) {
@@ -11065,44 +11074,6 @@ void addImageNameJNI(long objId, String name, long sequence,
 		}
 	}
 
-	/**
-	 * 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, path: %s, name: %s", metaAddr, path, name));
-			}
-		} catch (SQLException ex) {
-			throw new TskCoreException(String.format("Error looking up parent - meta addr: %d, path: %s, name: %s", metaAddr, path, name), ex);
-		} finally {
-			closeResultSet(resultSet);
-		}
-	}
-
 	/**
 	 * Add a file system file to the database. For use with the JNI callbacks
 	 * associated with the add image process.
@@ -11526,7 +11497,6 @@ private enum PREPARED_STATEMENT {
 		INSERT_POOL_INFO("INSERT INTO tsk_pool_info (obj_id, pool_type) VALUES (?, ?)"),
 		INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, data_source_obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)"
 				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
-		SELECT_OBJ_ID_BY_META_ADDR_AND_PATH("SELECT obj_id FROM tsk_files WHERE meta_addr = ? AND fs_obj_id = ? AND parent_path = ? AND name = ?"),
 		SELECT_TAG_NAME_BY_ID("SELECT * FROM tag_names where tag_name_id = ?");
 
 		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 4fe44e69095ea9e9a78621ed570f2a5b3417896d..104488c1e4fd1e6bf7e10b302d2ebcc6fd63099b 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -35,7 +35,9 @@
 import java.util.UUID;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import org.apache.commons.lang3.StringUtils;
 import org.sleuthkit.datamodel.TskData.TSK_FS_ATTR_TYPE_ENUM;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
 
 /**
  * A utility class that provides a interface to the SleuthKit via JNI. Supports
@@ -424,7 +426,7 @@ void free() throws TskCoreException {
 		 *                          case database.
 		 */
 		long addImageInfo(long deviceObjId, List<String> imageFilePaths, String timeZone, SleuthkitCase skCase) throws TskCoreException {
-			JniDbHelper dbHelper = new JniDbHelper(skCase);
+			JniDbHelper dbHelper = new JniDbHelper(skCase, new DefaultAddDataSourceCallbacks());
 			try {
 				long tskAutoDbPointer = initializeAddImgNat(dbHelper, timezoneLongToShort(timeZone), false, false, false);
 				runOpenAndAddImgNat(tskAutoDbPointer, UUID.randomUUID().toString(), imageFilePaths.toArray(new String[0]), imageFilePaths.size(), timeZone);				
@@ -468,9 +470,10 @@ public class AddImageProcess {
 			private final boolean skipFatFsOrphans;
 			private final String imageWriterPath;
 			private volatile long tskAutoDbPointer;
+			private long imageId = 0;
 			private boolean isCanceled;
 			private final SleuthkitCase skCase;
-			private final JniDbHelper dbHelper;
+			private JniDbHelper dbHelper;
 
 			/**
 			 * Constructs an object that encapsulates a multi-step process to
@@ -493,13 +496,11 @@ private AddImageProcess(String timeZone, boolean addUnallocSpace, boolean skipFa
 				tskAutoDbPointer = 0;
 				this.isCanceled = false;
 				this.skCase = skCase;
-				this.dbHelper = new JniDbHelper(skCase);
+				
 			}
 
 			/**
 			 * Starts the process of adding an image to the case database.
-			 * Either AddImageProcess.commit or AddImageProcess.revert MUST be
-			 * called after calling AddImageProcess.run.
 			 *
 			 * @param deviceId       An ASCII-printable identifier for the
 			 *                       device associated with the image that
@@ -515,6 +516,30 @@ private AddImageProcess(String timeZone, boolean addUnallocSpace, boolean skipFa
 			 *                          the process)
 			 */
 			public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws TskCoreException, TskDataException {
+				Image img = addImageToDatabase(skCase, imageFilePaths, sectorSize, "", "", "", "", deviceId);
+				run(deviceId, img, sectorSize, new DefaultAddDataSourceCallbacks());
+			}
+
+			/**
+			 * Starts the process of adding an image to the case database.
+			 *
+			 * @param deviceId       An ASCII-printable identifier for the
+			 *                       device associated with the image that
+			 *                       should be unique across multiple cases
+			 *                       (e.g., a UUID).
+			 * @param image          The image object (has already been added to the database)
+			 * @param sectorSize     The sector size (no longer used).
+			 * @param addDataSourceCallbacks  The callbacks to use to send data to ingest (may do nothing).
+			 *
+			 * @throws TskCoreException if a critical error occurs within the
+			 *                          SleuthKit.
+			 * @throws TskDataException if a non-critical error occurs within
+			 *                          the SleuthKit (should be OK to continue
+			 *                          the process)
+			 */
+			public void run(String deviceId, Image image, int sectorSize, 
+					AddDataSourceCallbacks addDataSourceCallbacks) throws TskCoreException, TskDataException {			
+				dbHelper = new JniDbHelper(skCase, addDataSourceCallbacks);
 				getTSKReadLock();
 				try {
 					long imageHandle = 0;
@@ -523,7 +548,7 @@ public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws
 							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, caseDbIdentifier);
+							imageHandle = image.getImageHandle();
 							tskAutoDbPointer = initAddImgNat(dbHelper, timezoneLongToShort(timeZone), addUnallocSpace, skipFatFsOrphans);
 						}
 						if (0 == tskAutoDbPointer) {
@@ -531,12 +556,13 @@ public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws
 						}
 					}
 					if (imageHandle != 0) {
-						runAddImgNat(tskAutoDbPointer, deviceId, imageHandle, timeZone, imageWriterPath);
+						runAddImgNat(tskAutoDbPointer, deviceId, imageHandle, image.getId(), timeZone, imageWriterPath);
 					}
 				} finally {
+					finishAddImageProcess();
 					releaseTSKReadLock();
 				}
-			}
+			}			
 
 			/**
 			 * Stops the process of adding the image to the case database that
@@ -558,56 +584,62 @@ public synchronized void stop() throws TskCoreException {
 					releaseTSKReadLock();
 				}
 			}
+			
+			/**
+			 * Call at the end of the add image process regardless of the error/canceled state.
+			 * 
+			 * Note that the new image is no longer deleted on error/cancellation
+			 * 
+			 * If the process was not canceled, will add the final batch of files to the database
+			 * and submit for any further processing through the callback. 
+			 * 
+			 * @throws TskCoreException 
+			 */
+			private synchronized void finishAddImageProcess() throws TskCoreException {
+				if (tskAutoDbPointer == 0) {
+					return;
+				}
+
+				// If the process wasn't cancelled, finish up processing the
+				// remaining files.
+				if (! this.isCanceled && dbHelper != null) {
+					dbHelper.finish();
+				}
+
+				// Free the auto DB pointer and get the image ID
+				imageId = finishAddImgNat(tskAutoDbPointer);
+				tskAutoDbPointer = 0;
+
+				skCase.addDataSourceToHasChildrenMap();
+			}			
 
 			/**
-			 * Rolls back the process of adding an image to the case database
-			 * that was started by calling AddImageProcess.run.
+			 * This no longer needs to be called.
 			 *
 			 * @throws TskCoreException if a critical error occurs within the
 			 *                          SleuthKit.
+			 * 
+			 * @deprecated No longer necessary
 			 */
+			@Deprecated
 			public synchronized void revert() throws TskCoreException {
-				getTSKReadLock();
-				try {
-					if (tskAutoDbPointer == 0) {
-						throw new TskCoreException("AddImgProcess::revert: AutoDB pointer is NULL");
-					}
-					
-					// Delete the object in the native code
-					finishAddImgNat(tskAutoDbPointer);
-					tskAutoDbPointer = 0;
-				} finally {
-					releaseTSKReadLock();
-				}
+				// No-op
 			}
 
 			/**
-			 * Completes the process of adding an image to the case database
-			 * that was started by calling AddImageProcess.run.
+			 * This no longer needs to be called. Will simply return the 
+			 * object ID of the new image.
 			 *
 			 * @return The object id of the image that was added.
 			 *
 			 * @throws TskCoreException if a critical error occurs within the
 			 *                          SleuthKit.
+			 * 
+			 * @deprecated No longer necessary
 			 */
+			@Deprecated
 			public synchronized long commit() throws TskCoreException {
-				getTSKReadLock();
-				try {
-					if (tskAutoDbPointer == 0) {
-						throw new TskCoreException("AddImgProcess::commit: AutoDB pointer is NULL");
-					}
-
-					dbHelper.finish();
-
-					// Get the image ID and delete the object in the native code
-					long id = finishAddImgNat(tskAutoDbPointer);
-					tskAutoDbPointer = 0;
-					
-					skCase.addDataSourceToHasChildrenMap();
-					return id;
-				} finally {
-					releaseTSKReadLock();
-				}
+				return imageId;
 			}
 
 			/**
@@ -642,8 +674,6 @@ public void run(String[] imageFilePaths) throws TskCoreException, TskDataExcepti
 
 			/**
 			 * Starts the process of adding an image to the case database.
-			 * Either AddImageProcess.commit or AddImageProcess.revert MUST be
-			 * called after calling AddImageProcess.run.
 			 *
 			 * @param deviceId       An ASCII-printable identifier for the
 			 *                       device associated with the image that
@@ -800,7 +830,6 @@ public static long openImage(String[] imageFiles, int sSize, SleuthkitCase skCas
 	 *                          TSK
 	 */
 	private static long openImage(String[] imageFiles, int sSize, boolean useCache, String caseIdentifer) throws TskCoreException {
-
 		getTSKReadLock();
 		try {
 			long imageHandle;
@@ -847,6 +876,105 @@ private static long openImage(String[] imageFiles, int sSize, boolean useCache,
 			releaseTSKReadLock();
 		}
 	}
+	
+	/**
+	 * This is a temporary measure to support opening an image at the beginning
+	 * of the add image process. The open image handle is put into the normal image cache so
+	 * it won't be opened a second time and it will be closed during case closing.
+	 * 
+	 * This will change when all image opens are done by object ID and not paths.
+	 * 
+	 * @param skCase      The case the image belongs to.
+	 * @param imagePaths  The complete list of paths for the image.
+	 * @param imageHandle The open image handle from TSK.
+	 */
+	private static void cacheImageHandle(SleuthkitCase skCase, List<String> imagePaths, long imageHandle) {
+		
+		// Construct the hash key from the image paths
+		StringBuilder keyBuilder = new StringBuilder();
+		for (int i = 0; i < imagePaths.size(); ++i) {
+			keyBuilder.append(imagePaths.get(i));
+		}
+		final String imageKey = keyBuilder.toString();
+		
+		// Get the case identifier
+		try {
+			String caseIdentifier = skCase.getUniqueCaseIdentifier();
+		
+			synchronized (HandleCache.cacheLock) {
+				HandleCache.getCaseHandles(caseIdentifier).fsHandleCache.put(imageHandle, new HashMap<>());
+				HandleCache.getCaseHandles(caseIdentifier).imageHandleCache.put(imageKey, imageHandle);
+			}
+		} catch (TskCoreException ex) {
+			// getUniqueCaseIdentfier() will only fail if the case is closed
+		}
+	}
+	
+	/**
+	 * Add an image to the database and return the open image.
+	 * 
+	 * @param skCase     The current case.
+	 * @param imagePaths The path(s) to the image (will just be the first for .e01, .001, etc).
+	 * @param sectorSize The sector size (0 for auto-detect).
+	 * @param timeZone   The time zone.
+	 * @param md5fromSettings        MD5 hash (if known).
+	 * @param sha1fromSettings       SHA1 hash (if known).
+	 * @param sha256fromSettings     SHA256 hash (if known).
+	 * @param deviceId   Device ID.
+	 * 
+	 * @return The Image object.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	public static Image addImageToDatabase(SleuthkitCase skCase, String[] imagePaths, int sectorSize,
+		String timeZone, String md5fromSettings, String sha1fromSettings, String sha256fromSettings, String deviceId) throws TskCoreException {
+		
+		// Open the image
+		long imageHandle = openImgNat(imagePaths, 1, sectorSize);
+		
+		// Get the fields stored in the native code
+		List<String> computedPaths = Arrays.asList(getPathsForImageNat(imageHandle));
+		long size = getSizeForImageNat(imageHandle);
+		long type = getTypeForImageNat(imageHandle);
+		long computedSectorSize = getSectorSizeForImageNat(imageHandle);
+		String md5 = md5fromSettings;
+		if (StringUtils.isEmpty(md5)) {
+			md5 = getMD5HashForImageNat(imageHandle);
+		}
+		String sha1 = sha1fromSettings;
+		if (StringUtils.isEmpty(sha1)) {
+			sha1 = getSha1HashForImageNat(imageHandle);
+		}
+		// Sleuthkit does not currently generate any SHA256 hashes. Set to empty
+		// string for consistency.
+		String sha256 = sha256fromSettings;
+		if (sha256 == null) {
+			sha256 = "";
+		}
+		String collectionDetails = getCollectionDetailsForImageNat(imageHandle);
+		
+		//  Now save to database
+		CaseDbTransaction transaction = skCase.beginTransaction();
+		try {
+			Image img = skCase.addImage(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), computedSectorSize, 
+				size, null, computedPaths, 
+				timeZone, md5, sha1, sha256, 
+				deviceId, transaction);
+			if (!StringUtils.isEmpty(collectionDetails)) {
+				skCase.setAcquisitionDetails(img, collectionDetails);
+			}
+			transaction.commit();
+			
+		    img.setImageHandle(imageHandle);
+			cacheImageHandle(skCase, computedPaths, imageHandle);
+			return img;
+		} catch (TskCoreException ex) {
+			transaction.rollback();
+			throw(ex);
+		}
+	}
+	
+	
 
 	/**
 	 * Get volume system Handle
@@ -1963,7 +2091,7 @@ public static long openFile(long fsHandle, long fileId, TSK_FS_ATTR_TYPE_ENUM at
 
 	private static native void runOpenAndAddImgNat(long process, String deviceId, String[] imgPath, int splits, String timezone) throws TskCoreException, TskDataException;
 
-	private static native void runAddImgNat(long process, String deviceId, long a_img_info, String timeZone, String imageWriterPath) throws TskCoreException, TskDataException;
+	private static native void runAddImgNat(long process, String deviceId, long a_img_info, long image_id, String timeZone, String imageWriterPath) throws TskCoreException, TskDataException;
 
 	private static native void stopAddImgNat(long process) throws TskCoreException;
 
@@ -1996,6 +2124,20 @@ public static long openFile(long fsHandle, long fileId, TSK_FS_ATTR_TYPE_ENUM at
 	private static native int readFileNat(long fileHandle, byte[] readBuffer, long offset, int offset_type, long len) throws TskCoreException;
 
 	private static native int saveFileMetaDataTextNat(long fileHandle, String fileName) throws TskCoreException;
+	
+	private static native String[] getPathsForImageNat(long imgHandle);
+	
+	private static native long getSizeForImageNat(long imgHandle);
+	
+	private static native long getTypeForImageNat(long imgHandle);
+	
+	private static native long getSectorSizeForImageNat(long imgHandle);
+	
+	private static native String getMD5HashForImageNat(long imgHandle);
+	
+	private static native String getSha1HashForImageNat(long imgHandle);
+	
+	private static native String getCollectionDetailsForImageNat(long imgHandle);
 
 	private static native void closeImgNat(long imgHandle);