diff --git a/bindings/java/jni/dataModel_SleuthkitJNI.cpp b/bindings/java/jni/dataModel_SleuthkitJNI.cpp
index 4d53b523d6bf4745dabe4255e6bb62e82725bbc8..e9c8e16af4be76155b4f0719d3580f43e47c7608 100644
--- a/bindings/java/jni/dataModel_SleuthkitJNI.cpp
+++ b/bindings/java/jni/dataModel_SleuthkitJNI.cpp
@@ -282,14 +282,20 @@ JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_newCaseDbMulti
     TSK_TCHAR dbPathT[1024];
     toTCHAR(env, dbPathT, 1024, dbName);
 
-    CaseDbConnectionInfo info(env->GetStringUTFChars(host, false),
-        env->GetStringUTFChars(port, false),
-        env->GetStringUTFChars(user, false),
-        env->GetStringUTFChars(pass, false),
-        (CaseDbConnectionInfo::DbType)dbType);
+    const char* host_utf8 = env->GetStringUTFChars(host, NULL);
+    const char* port_utf8 = env->GetStringUTFChars(port, NULL);
+    const char* user_utf8 = env->GetStringUTFChars(user, NULL);
+    const char* pass_utf8 = env->GetStringUTFChars(pass, NULL);
+    CaseDbConnectionInfo info(host_utf8, port_utf8, user_utf8, pass_utf8, (CaseDbConnectionInfo::DbType)dbType);
 
     TskCaseDb *tskCase = TskCaseDb::newDb(dbPathT, &info);
 
+    // free memory allocated by env->GetStringUTFChars()
+    env->ReleaseStringUTFChars(host, host_utf8);
+    env->ReleaseStringUTFChars(port, port_utf8);
+    env->ReleaseStringUTFChars(user, user_utf8);
+    env->ReleaseStringUTFChars(pass, pass_utf8);
+
     if (tskCase == NULL) {
         setThrowTskCoreError(env);
         return 0;
@@ -317,14 +323,20 @@ JNIEXPORT jlong JNICALL Java_org_sleuthkit_datamodel_SleuthkitJNI_openCaseDbMult
     TSK_TCHAR dbPathT[1024];
     toTCHAR(env, dbPathT, 1024, dbName);
 
-    CaseDbConnectionInfo info(env->GetStringUTFChars(host, false),
-        env->GetStringUTFChars(port, false),
-        env->GetStringUTFChars(user, false),
-        env->GetStringUTFChars(pass, false),
-		(CaseDbConnectionInfo::DbType)dbType);
+    const char* host_utf8 = env->GetStringUTFChars(host, NULL);
+    const char* port_utf8 = env->GetStringUTFChars(port, NULL);
+    const char* user_utf8 = env->GetStringUTFChars(user, NULL);
+    const char* pass_utf8 = env->GetStringUTFChars(pass, NULL);
+    CaseDbConnectionInfo info(host_utf8, port_utf8, user_utf8, pass_utf8, (CaseDbConnectionInfo::DbType)dbType);
 
     TskCaseDb *tskCase = TskCaseDb::openDb(dbPathT, &info);
 
+    // free memory allocated by env->GetStringUTFChars()
+    env->ReleaseStringUTFChars(host, host_utf8);
+    env->ReleaseStringUTFChars(port, port_utf8);
+    env->ReleaseStringUTFChars(user, user_utf8);
+    env->ReleaseStringUTFChars(pass, pass_utf8);
+
     if (tskCase == NULL) {
         setThrowTskCoreError(env);
         return 0;
@@ -623,8 +635,8 @@ JNIEXPORT jboolean JNICALL
         return (jboolean)false;
     }
 
-    return (jboolean)((tsk_hdb_uses_external_indexes(db) == static_cast<uint8_t>(1)) && 
-                      (!tsk_hdb_is_idx_only(db) == static_cast<uint8_t>(1)));
+    return (jboolean)((tsk_hdb_uses_external_indexes(db) == 1) && 
+                      (tsk_hdb_is_idx_only(db) == 0));
 }
  
 /**
diff --git a/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java b/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
index 9bbb6da187e1657d04b1f4b400e594c8695766f1..612256a0bd4e005d6d9bb93e8b3820a76536a871 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
@@ -1,7 +1,7 @@
 /*
  * Sleuth Kit Data Model
  *
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties
index 5337405dbc38e2d41c80d527dc400de30feebd76..1ee3ae2c7716b76574c43d6b74b0639a4ef12bcd 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties
+++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties
@@ -215,4 +215,9 @@ DatabaseConnectionCheck.Installation=Issue with installation. JDBC driver not fo
 DatabaseConnectionCheck.MissingHostname=Missing hostname.
 DatabaseConnectionCheck.MissingPort=Missing port number.
 DatabaseConnectionCheck.MissingUsername=Missing username.
-DatabaseConnectionCheck.MissingPassword=Missing password.
\ No newline at end of file
+DatabaseConnectionCheck.MissingPassword=Missing password.
+IngestJobInfo.IngestJobStatusType.Started.displayName=Started
+IngestJobInfo.IngestJobStatusType.Cancelled.displayName=Cancelled
+IngestJobInfo.IngestJobStatusType.Completed.displayName=Completed
+IngestModuleInfo.IngestModuleType.FileLevel.displayName=File Level
+IngestModuleInfo.IngestModuleType.DataSourceLevel.displayName=Data Source Level
\ No newline at end of file
diff --git a/bindings/java/src/org/sleuthkit/datamodel/IngestJobInfo.java b/bindings/java/src/org/sleuthkit/datamodel/IngestJobInfo.java
new file mode 100755
index 0000000000000000000000000000000000000000..ef052021ef32816fe3054b8160044b50f29555e7
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/IngestJobInfo.java
@@ -0,0 +1,211 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2011-2016 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.Date;
+import java.util.List;
+import java.util.ResourceBundle;
+
+/**
+ * Represents information for an ingest job.
+ */
+public final class IngestJobInfo {
+
+	private static final ResourceBundle bundle = ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle");
+
+	public enum IngestJobStatusType {
+
+		//DO NOT CHANGE ORDER
+		STARTED(bundle.getString("IngestJobInfo.IngestJobStatusType.Started.displayName")),
+		CANCELLED(bundle.getString("IngestJobInfo.IngestJobStatusType.Cancelled.displayName")),
+		COMPLETED(bundle.getString("IngestJobInfo.IngestJobStatusType.Completed.displayName"));
+
+		private String displayName;
+
+		private IngestJobStatusType(String displayName) {
+			this.displayName = displayName;
+		}
+
+		public static IngestJobStatusType fromID(int typeId) {
+			for (IngestJobStatusType statusType : IngestJobStatusType.values()) {
+				if (statusType.ordinal() == typeId) {
+					return statusType;
+				}
+			}
+			return null;
+		}
+
+		/**
+		 * @return the displayName
+		 */
+		public String getDisplayName() {
+			return displayName;
+		}
+	}
+
+	private final long ingestJobId;
+	private final long objectId;
+	private final String hostName;
+	private final Date startDateTime;
+	private Date endDateTime = new Date(0);
+	private final String settingsDir;
+	private final List<IngestModuleInfo> ingestModuleInfo;
+	private final SleuthkitCase skCase;
+	private IngestJobStatusType status;
+
+	/**
+	 * Constructs an IngestJobInfo that has not ended
+	 *
+	 * @param ingestJobId      The id of the ingest job
+	 * @param objectId     The data source the job is being run on
+	 * @param hostName         The host on which the job was executed
+	 * @param startDateTime    The date time the job was started
+	 * @param settingsDir      The directory of the job settings
+	 * @param ingestModuleInfo The ingest modules being run for this job
+	 * @param skCase           A reference to sleuthkit case
+	 */
+	IngestJobInfo(long ingestJobId, long objectId, String hostName, Date startDateTime, String settingsDir, List<IngestModuleInfo> ingestModuleInfo, SleuthkitCase skCase) {
+		this.ingestJobId = ingestJobId;
+		this.objectId = objectId;
+		this.hostName = hostName;
+		this.startDateTime = startDateTime;
+		this.settingsDir = settingsDir;
+		this.skCase = skCase;
+		this.ingestModuleInfo = ingestModuleInfo;
+		this.status = IngestJobStatusType.STARTED;
+	}
+
+	/**
+	 * Constructs an IngestJobInfo that has already ended
+	 *
+	 * @param ingestJobId      The id of the ingest job
+	 * @param dataSourceId     The data source the job is being run on
+	 * @param hostName         The host on which the job was executed
+	 * @param startDateTime    The date time the job was started
+	 * @param endDateTime      The date time the job was ended (if it ended)
+	 * @param status           The status of the job
+	 * @param settingsDir      The directory of the job settings
+	 * @param ingestModuleInfo The ingest modules being run for this job
+	 * @param skCase           A reference to sleuthkit case
+	 */
+	IngestJobInfo(long ingestJobId, long dataSourceId, String hostName, Date startDateTime, Date endDateTime, IngestJobStatusType status, String settingsDir, List<IngestModuleInfo> ingestModuleInfo, SleuthkitCase skCase) {
+		this.ingestJobId = ingestJobId;
+		this.objectId = dataSourceId;
+		this.hostName = hostName;
+		this.startDateTime = startDateTime;
+		this.endDateTime = endDateTime;
+		this.settingsDir = settingsDir;
+		this.skCase = skCase;
+		this.ingestModuleInfo = ingestModuleInfo;
+		this.status = status;
+	}
+
+	/**
+	 * @return the end date time of the job (equal to the epoch if it has not
+	 *         been set yet).
+	 */
+	public Date getEndDateTime() {
+		return endDateTime;
+	}
+
+	/**
+	 * Sets the end date for the ingest job info, and updates the database.
+	 *
+	 * @param endDateTime the endDateTime to set
+	 *
+	 * @throws org.sleuthkit.datamodel.TskCoreException
+	 */
+	public void setEndDateTime(Date endDateTime) throws TskCoreException {
+		Date oldDate = this.endDateTime;
+		this.endDateTime = endDateTime;
+		try {
+			skCase.setIngestJobEndDateTime(getIngestJobId(), endDateTime.getTime());
+		} catch (TskCoreException ex) {
+			this.endDateTime = oldDate;
+			throw ex;
+		}
+	}
+
+	/**
+	 * Sets the ingest status for the ingest job info, and updates the database.
+	 *
+	 * @param status The new status
+	 *
+	 * @throws TskCoreException
+	 */
+	public void setIngestJobStatus(IngestJobStatusType status) throws TskCoreException {
+		IngestJobStatusType oldStatus = this.getStatus();
+		this.status = status;
+		try {
+			skCase.setIngestJobStatus(getIngestJobId(), status);
+		} catch (TskCoreException ex) {
+			this.status = oldStatus;
+			throw ex;
+		}
+	}
+
+	/**
+	 * @return the ingestJobId
+	 */
+	public long getIngestJobId() {
+		return ingestJobId;
+	}
+
+	/**
+	 * @return the objectId
+	 */
+	public long getObjectId() {
+		return objectId;
+	}
+
+	/**
+	 * @return the hostName
+	 */
+	public String getHostName() {
+		return hostName;
+	}
+
+	/**
+	 * @return the startDateTime
+	 */
+	public Date getStartDateTime() {
+		return startDateTime;
+	}
+
+	/**
+	 * @return the settingsDir
+	 */
+	public String getSettingsDir() {
+		return settingsDir;
+	}
+
+	/**
+	 * @return the ingestModuleInfo
+	 */
+	public List<IngestModuleInfo> getIngestModuleInfo() {
+		return ingestModuleInfo;
+	}
+
+	/**
+	 * @return the status
+	 */
+	public IngestJobStatusType getStatus() {
+		return status;
+	}
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java b/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java
new file mode 100755
index 0000000000000000000000000000000000000000..bec082446464559c1b1c4b89aababacb46e3f919
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2011-2016 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.ResourceBundle;
+
+/**
+ * Class representing information about an ingest module, used in ingest job
+ * info to show which ingest modules were run.
+ */
+public final class IngestModuleInfo {
+
+	private static final ResourceBundle bundle = ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle");
+
+	/**
+	 * Used to keep track of the module types
+	 */
+	public static enum IngestModuleType {
+		//DO NOT CHANGE ORDER
+		DATA_SOURCE_LEVEL(bundle.getString("IngestModuleInfo.IngestModuleType.DataSourceLevel.displayName")),
+		FILE_LEVEL(bundle.getString("IngestModuleInfo.IngestModuleType.FileLevel.displayName"));
+		
+		private String displayName;
+		
+		private IngestModuleType(String displayName) {
+			this.displayName = displayName;
+		}
+
+		public static IngestModuleType fromID(int typeId) {
+			for (IngestModuleType moduleType : IngestModuleType.values()) {
+				if (moduleType.ordinal() == typeId) {
+					return moduleType;
+				}
+			}
+			return null;
+		}
+
+		/**
+		 * @return the displayName
+		 */
+		public String getDisplayName() {
+			return displayName;
+		}
+
+	}
+
+	private final long ingestModuleId;
+	private final String displayName;
+	private final String uniqueName;
+	private final IngestModuleType type;
+	private final String version;
+
+	/**
+	 *
+	 * @param ingestModuleId The id of the ingest module
+	 * @param displayName    The display name of the ingest module
+	 * @param uniqueName     The unique name of the ingest module.
+	 * @param type           The ingest module type of the module.
+	 * @param version        The version number of the module.
+	 */
+	IngestModuleInfo(long ingestModuleId, String displayName, String uniqueName, IngestModuleType type, String version) {
+		this.ingestModuleId = ingestModuleId;
+		this.displayName = displayName;
+		this.uniqueName = uniqueName;
+		this.type = type;
+		this.version = version;
+	}
+
+	/**
+	 * @return the ingestModuleId
+	 */
+	public long getIngestModuleId() {
+		return ingestModuleId;
+	}
+
+	/**
+	 * @return the displayName
+	 */
+	public String getDisplayName() {
+		return displayName;
+	}
+
+	/**
+	 * @return the uniqueName
+	 */
+	public String getUniqueName() {
+		return uniqueName;
+	}
+
+	/**
+	 * @return the typeID
+	 */
+	public IngestModuleType getType() {
+		return type;
+	}
+
+	/**
+	 * @return the version
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
old mode 100644
new mode 100755
index 48390752e5e0fb5e2660150d5bdcd57ccb92c0a3..7e6701df4d12df7dac0a4e4b8c2938410f6344ff
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -42,6 +42,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -59,6 +60,8 @@
 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
 import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE;
+import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType;
+import org.sleuthkit.datamodel.IngestModuleInfo.IngestModuleType;
 import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess;
 import org.sleuthkit.datamodel.TskData.DbType;
 import org.sleuthkit.datamodel.TskData.FileKnown;
@@ -200,6 +203,13 @@ private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbTyp
 		this.connections = new SQLiteConnections(dbPath);
 		init(caseHandle);
 		updateDatabaseSchema(dbPath);
+		// Initializing ingest module types is done here because it is possible 
+		// the table is not there when init is called. It must be there after
+		// the schema update.
+		CaseDbConnection connection = connections.getConnection();
+		this.initIngestModuleTypes(connection);
+		this.initIngestStatusTypes(connection);
+		connection.close();
 		logSQLiteJDBCDriverInfo();
 	}
 
@@ -227,6 +237,13 @@ private SleuthkitCase(String host, int port, String dbName, String userName, Str
 		this.connections = new PostgreSQLConnections(host, port, dbName, userName, password);
 		init(caseHandle);
 		updateDatabaseSchema(null);
+		// Initializing ingest module types is done here because it is possible 
+		// the table is not there when init is called. It must be there after
+		// the schema update.
+		CaseDbConnection connection = connections.getConnection();
+		this.initIngestModuleTypes(connection);
+		this.initIngestStatusTypes(connection);
+		connection.close();
 	}
 
 	private void init(SleuthkitJNI.CaseDbHandle caseHandle) throws Exception {
@@ -335,6 +352,48 @@ private void initNextArtifactId() throws TskCoreException, SQLException {
 		}
 	}
 
+	private void initIngestModuleTypes(CaseDbConnection connection) throws TskCoreException {
+		Statement s = null;
+		ResultSet rs = null;
+		try {
+			s = connection.createStatement();
+			for (IngestModuleType type : IngestModuleType.values()) {
+				rs = connection.executeQuery(s, "SELECT type_id FROM ingest_module_types WHERE type_id=" + type.ordinal() + ";");
+				if (!rs.next()) {
+					s.execute("INSERT INTO ingest_module_types (type_id, type_name) VALUES (" + type.ordinal() + ", '" + type.toString() + "');");
+				}
+				rs.close();
+				rs = null;
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error adding ingest module types to table.", ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+		}
+	}
+
+	private void initIngestStatusTypes(CaseDbConnection connection) throws TskCoreException {
+		Statement s = null;
+		ResultSet rs = null;
+		try {
+			s = connection.createStatement();
+			for (IngestJobStatusType type : IngestJobStatusType.values()) {
+				rs = connection.executeQuery(s, "SELECT type_id FROM ingest_job_status_types WHERE type_id=" + type.ordinal() + ";");
+				if (!rs.next()) {
+					s.execute("INSERT INTO ingest_job_status_types (type_id, type_name) VALUES (" + type.ordinal() + ", '" + type.toString() + "');");
+				}
+				rs.close();
+				rs = null;
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error adding ingest module types to table.", ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+		}
+	}
+
 	/**
 	 * Modify the case database to bring it up-to-date with the current version
 	 * of the database schema.
@@ -686,6 +745,19 @@ private int updateFromSchema3toSchema4(int schemaVersionNumber, CaseDbConnection
 				updateStatement.executeUpdate("UPDATE tsk_files SET data_source_obj_id = " + dataSourceId + " WHERE obj_id = " + fileId + ";");
 			}
 			resultSet.close();
+			statement.execute("CREATE TABLE ingest_module_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)"); //NON-NLS
+			statement.execute("CREATE TABLE ingest_job_status_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)"); //NON-NLS
+			if (this.dbType.equals(DbType.SQLITE)) {
+				statement.execute("CREATE TABLE ingest_modules (ingest_module_id INTEGER PRIMARY KEY, display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id));"); //NON-NLS
+				statement.execute("CREATE TABLE ingest_jobs (ingest_job_id INTEGER PRIMARY KEY, obj_id BIGINT NOT NULL, host_name TEXT NOT NULL, start_date_time BIGINT NOT NULL, end_date_time BIGINT NOT NULL, status_id INTEGER NOT NULL, settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id));"); //NON-NLS
+			} else {
+				statement.execute("CREATE TABLE ingest_modules (ingest_module_id BIGSERIAL PRIMARY KEY, display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id));"); //NON-NLS
+				statement.execute("CREATE TABLE ingest_jobs (ingest_job_id BIGSERIAL PRIMARY KEY, obj_id BIGINT NOT NULL, host_name TEXT NOT NULL, start_date_time BIGINT NOT NULL, end_date_time BIGINT NOT NULL, status_id INTEGER NOT NULL, settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id));"); //NON-NLS
+			}
+
+			statement.execute("CREATE TABLE ingest_job_modules (ingest_job_id INTEGER, ingest_module_id INTEGER, pipeline_position INTEGER, PRIMARY KEY(ingest_job_id, ingest_module_id), FOREIGN KEY(ingest_job_id) REFERENCES ingest_jobs(ingest_job_id), FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id));"); //NON-NLS
+			initIngestModuleTypes(connection);
+			initIngestStatusTypes(connection);
 
 			return 4;
 
@@ -5857,7 +5929,7 @@ public void deleteReport(Report report) throws TskCoreException {
 		acquireSharedLock();
 		try {
 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.DELETE_REPORT);
-			statement.setString(1, String.valueOf(report.getId()));
+			statement.setLong(1, report.getId());
 			connection.executeUpdate(statement);
 		} catch (SQLException ex) {
 			throw new TskCoreException("Error querying reports table", ex);
@@ -5887,6 +5959,213 @@ private static void closeStatement(Statement statement) {
 		}
 	}
 
+	/**
+	 * Sets the end date for the given ingest job
+	 *
+	 * @param ingestJobId The ingest job to set the end date for
+	 * @param endDateTime The end date
+	 *
+	 * @throws TskCoreException If inserting into the database fails
+	 */
+	void setIngestJobEndDateTime(long ingestJobId, long endDateTime) throws TskCoreException {
+		CaseDbConnection connection = connections.getConnection();
+		acquireSharedLock();
+		try {
+			Statement statement = connection.createStatement();
+			statement.executeUpdate("UPDATE ingest_jobs SET end_date_time=" + endDateTime + " WHERE ingest_job_id=" + ingestJobId + ";");
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error updating the end date (ingest_job_id = " + ingestJobId + ".", ex);
+		} finally {
+			connection.close();
+			releaseSharedLock();
+		}
+	}
+
+	void setIngestJobStatus(long ingestJobId, IngestJobStatusType status) throws TskCoreException {
+		CaseDbConnection connection = connections.getConnection();
+		acquireSharedLock();
+		try {
+			Statement statement = connection.createStatement();
+			statement.executeUpdate("UPDATE ingest_jobs SET status_id=" + status.ordinal() + " WHERE ingest_job_id=" + ingestJobId + ";");
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error ingest job status (ingest_job_id = " + ingestJobId + ".", ex);
+		} finally {
+			connection.close();
+			releaseSharedLock();
+		}
+	}
+
+	/**
+	 *
+	 * @param dataSource    The datasource the ingest job is being run on
+	 * @param hostName      The name of the host
+	 * @param ingestModules The ingest modules being run during the ingest job.
+	 *                      Should be in pipeline order.
+	 * @param jobStart      The time the job started
+	 * @param jobEnd        The time the job ended
+	 * @param status        The ingest job status
+	 * @param settingsDir   The directory of the job's settings
+	 *
+	 * @return An information object representing the ingest job added to the
+	 *         database.
+	 *
+	 * @throws TskCoreException If adding the job to the database fails.
+	 */
+	public final IngestJobInfo addIngestJob(Content dataSource, String hostName, List<IngestModuleInfo> ingestModules, Date jobStart, Date jobEnd, IngestJobStatusType status, String settingsDir) throws TskCoreException {
+		CaseDbConnection connection = connections.getConnection();
+		acquireSharedLock();
+		ResultSet resultSet = null;
+		Statement statement = null;
+		try {
+			connection.beginTransaction();
+			statement = connection.createStatement();
+			PreparedStatement insertStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_INGEST_JOB, Statement.RETURN_GENERATED_KEYS);
+			insertStatement.setLong(1, dataSource.getId());
+			insertStatement.setString(2, hostName);
+			insertStatement.setLong(3, jobStart.getTime());
+			insertStatement.setLong(4, jobEnd.getTime());
+			insertStatement.setInt(5, status.ordinal());
+			insertStatement.setString(6, settingsDir);
+			connection.executeUpdate(insertStatement);
+			resultSet = insertStatement.getGeneratedKeys();
+			resultSet.next();
+			long id = resultSet.getLong(1);
+			for (int i = 0; i < ingestModules.size(); i++) {
+				IngestModuleInfo ingestModule = ingestModules.get(i);
+				statement.executeUpdate("INSERT INTO ingest_job_modules (ingest_job_id, ingest_module_id, pipeline_position) "
+						+ "VALUES (" + id + ", " + ingestModule.getIngestModuleId() + ", " + i + ");");
+			}
+			resultSet.close();
+			resultSet = null;
+			connection.commitTransaction();
+			return new IngestJobInfo(id, dataSource.getId(), hostName, jobStart, "", ingestModules, this);
+		} catch (SQLException ex) {
+			connection.rollbackTransaction();
+			throw new TskCoreException("Error adding the ingest job.", ex);
+		} finally {
+			closeResultSet(resultSet);
+			connection.close();
+			releaseSharedLock();
+		}
+	}
+
+	/**
+	 * Adds the given ingest module to the database.
+	 *
+	 * @param displayName The display name of the module
+	 * @param uniqueName  The factory class name of the module.
+	 * @param type        The type of the module.
+	 * @param version     The version of the module.
+	 *
+	 * @return An ingest module info object representing the module added to the
+	 *         db.
+	 *
+	 * @throws TskCoreException When the ingest module cannot be added.
+	 */
+	public final IngestModuleInfo addIngestModule(String displayName, String factoryClassName, IngestModuleType type, String version) throws TskCoreException {
+		CaseDbConnection connection = connections.getConnection();
+		ResultSet resultSet = null;
+		Statement statement = null;
+		String uniqueName = factoryClassName + "-" + displayName + "-" + type.toString() + "-" + version;
+		try {
+			statement = connection.createStatement();
+			resultSet = statement.executeQuery("SELECT * FROM ingest_modules WHERE unique_name = '" + uniqueName + "'");
+			if (!resultSet.next()) {
+				resultSet.close();
+				resultSet = null;
+				PreparedStatement insertStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_INGEST_MODULE, Statement.RETURN_GENERATED_KEYS);
+				insertStatement.setString(1, displayName);
+				insertStatement.setString(2, uniqueName);
+				insertStatement.setInt(3, type.ordinal());
+				insertStatement.setString(4, version);
+				connection.executeUpdate(insertStatement);
+				resultSet = statement.getGeneratedKeys();
+				resultSet.next();
+				long id = resultSet.getLong(1);
+				resultSet.close();
+				resultSet = null;
+				return new IngestModuleInfo(id, displayName, uniqueName, type, version);
+			} else {
+				return new IngestModuleInfo(resultSet.getInt("ingest_module_id"), resultSet.getString("display_name"), resultSet.getString("unique_name"), IngestModuleType.fromID(resultSet.getInt("type_id")), resultSet.getString("version"));
+			}
+		} catch (SQLException ex) {
+			try {
+				resultSet = statement.executeQuery("SELECT * FROM ingest_modules WHERE unique_name = '" + uniqueName + "'");
+				if (resultSet.next()) {
+					return new IngestModuleInfo(resultSet.getInt("ingest_module_id"), resultSet.getString("display_name"), uniqueName, IngestModuleType.fromID(resultSet.getInt("type_id")), resultSet.getString("version"));
+				} else {
+					throw new TskCoreException("Couldn't add new module to database.", ex);
+				}
+			} catch (SQLException ex1) {
+				throw new TskCoreException("Couldn't add new module to database.", ex1);
+			}
+		} finally {
+			closeResultSet(resultSet);
+			closeStatement(statement);
+			connection.close();
+		}
+	}
+
+	/**
+	 * Gets all of the ingest jobs that have been run.
+	 *
+	 * @return The information about the ingest jobs that have been run
+	 *
+	 * @throws TskCoreException If there is a problem getting the ingest jobs
+	 */
+	public final List<IngestJobInfo> getIngestJobs() throws TskCoreException {
+		CaseDbConnection connection = connections.getConnection();
+		ResultSet resultSet = null;
+		Statement statement = null;
+		List<IngestJobInfo> ingestJobs = new ArrayList<IngestJobInfo>();
+		try {
+			statement = connection.createStatement();
+			resultSet = statement.executeQuery("SELECT * FROM ingest_jobs");
+			while (resultSet.next()) {
+				ingestJobs.add(new IngestJobInfo(resultSet.getInt("ingest_job_id"), resultSet.getLong("obj_id"), resultSet.getString("host_name"), new Date(resultSet.getLong("start_date_time")), new Date(resultSet.getLong("end_date_time")), IngestJobStatusType.fromID(resultSet.getInt("status_id")), resultSet.getString("settings_dir"), this.getIngestModules(resultSet.getInt("ingest_job_id"), connection), this));
+			}
+			return ingestJobs;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Couldn't get the ingest jobs.", ex);
+		} finally {
+			closeResultSet(resultSet);
+			closeStatement(statement);
+			connection.close();
+		}
+	}
+
+	/**
+	 * Gets the ingest modules associated with the ingest job
+	 *
+	 * @param ingestJobId The id of the ingest job to get ingest modules for
+	 * @param connection  The database connection
+	 *
+	 * @return The ingest modules of the job
+	 *
+	 * @throws SQLException If it fails to get the modules from the db.
+	 */
+	private List<IngestModuleInfo> getIngestModules(int ingestJobId, CaseDbConnection connection) throws SQLException {
+		ResultSet resultSet = null;
+		Statement statement = null;
+		List<IngestModuleInfo> ingestModules = new ArrayList<IngestModuleInfo>();
+		try {
+			statement = connection.createStatement();
+			resultSet = statement.executeQuery("SELECT ingest_job_modules.ingest_module_id, ingest_job_modules.pipeline_position, ingest_modules.display_name, ingest_modules.unique_name, "
+					+ "ingest_modules.type_id, ingest_modules.version "
+					+ "FROM ingest_job_modules, ingest_modules "
+					+ "WHERE ingest_job_modules.ingest_job_id = " + ingestJobId + " "
+					+ "AND ingest_modules.ingest_module_id = ingest_job_modules.ingest_module_id "
+					+ "ORDER BY (ingest_job_modules.pipeline_position);");
+			while (resultSet.next()) {
+				ingestModules.add(new IngestModuleInfo(resultSet.getInt("ingest_module_id"), resultSet.getString("display_name"), resultSet.getString("unique_name"), IngestModuleType.fromID(resultSet.getInt("type_id")), resultSet.getString("version")));
+			}
+			return ingestModules;
+		} finally {
+			closeResultSet(resultSet);
+			closeStatement(statement);
+		}
+	}
+
 	/**
 	 * Notifies observers of errors in the SleuthkitCase.
 	 */
@@ -6019,7 +6298,9 @@ private enum PREPARED_STATEMENT {
 		SELECT_ARTIFACT_TAGS_BY_ARTIFACT("SELECT * FROM blackboard_artifact_tags INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id WHERE blackboard_artifact_tags.artifact_id = ?"), //NON-NLS
 		SELECT_REPORTS("SELECT * FROM reports"), //NON-NLS
 		INSERT_REPORT("INSERT INTO reports (path, crtime, src_module_name, report_name) VALUES (?, ?, ?, ?)"), //NON-NLS
-		DELETE_REPORT("DELETE FROM reports WHERE reports.report_id = ?"); //NON-NLS
+		DELETE_REPORT("DELETE FROM reports WHERE reports.report_id = ?"), //NON-NLS
+		INSERT_INGEST_JOB("INSERT INTO ingest_jobs (obj_id, host_name, start_date_time, end_date_time, status_id, settings_dir) VALUES (?, ?, ?, ?, ?, ?)"), //NON-NLS
+		INSERT_INGEST_MODULE("INSERT INTO ingest_modules (display_name, unique_name, type_id, version) VALUES(?, ?, ?, ?)"); //NON-NLS
 
 		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 b8ad7d998d11ed7f5f2a1552ace1a6aa28875179..338f58ae18908961ea50ffeb0b83ec6db369f33b 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -154,6 +154,8 @@ public class SleuthkitJNI {
 	private SleuthkitJNI() {
 
 	}
+	
+	
 
 	/**
 	 * Handle to TSK Case database
diff --git a/tsk/auto/db_postgresql.cpp b/tsk/auto/db_postgresql.cpp
index edb795805545293458c5f9ceae94116608b158ea..8def8e00ab24531ac0c0e2c0ad6219785c73de21 100755
--- a/tsk/auto/db_postgresql.cpp
+++ b/tsk/auto/db_postgresql.cpp
@@ -560,6 +560,26 @@ int TskDbPostgreSQL::initialize() {
         attempt_exec
         ("CREATE TABLE tsk_vs_parts (obj_id BIGSERIAL PRIMARY KEY, addr BIGINT NOT NULL, start BIGINT NOT NULL, length BIGINT NOT NULL, descr TEXT, flags INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id));",
         "Error creating tsk_vol_info table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_module_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)",
+        "Error creating ingest_module_types table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_job_status_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)",
+        "Error creating ingest_job_status_types table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_modules (ingest_module_id BIGSERIAL PRIMARY KEY, display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id));",
+        "Error creating ingest_modules table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_jobs (ingest_job_id BIGSERIAL PRIMARY KEY, obj_id BIGINT NOT NULL, host_name TEXT NOT NULL, start_date_time BIGINT NOT NULL, end_date_time BIGINT NOT NULL, status_id INTEGER NOT NULL, settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id));",
+        "Error creating ingest_jobs table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_job_modules (ingest_job_id INTEGER, ingest_module_id INTEGER, pipeline_position INTEGER, PRIMARY KEY(ingest_job_id, ingest_module_id), FOREIGN KEY(ingest_job_id) REFERENCES ingest_jobs(ingest_job_id), FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id));",
+        "Error creating ingest_job_modules table: %s\n")
         ||
         attempt_exec
         ("CREATE TABLE reports (report_id BIGSERIAL PRIMARY KEY, path TEXT NOT NULL, crtime INTEGER NOT NULL, src_module_name TEXT NOT NULL, report_name TEXT NOT NULL)","Error creating reports table: %s\n")) {
diff --git a/tsk/auto/db_sqlite.cpp b/tsk/auto/db_sqlite.cpp
index 6c42eef7edbf4d08ece6e5a3e93af8a4af499d16..bea0b9732c6a442998af0adcf2bc9567b97e1975 100755
--- a/tsk/auto/db_sqlite.cpp
+++ b/tsk/auto/db_sqlite.cpp
@@ -321,6 +321,26 @@ int
         attempt_exec
         ("CREATE TABLE blackboard_attribute_types (attribute_type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL, display_name TEXT, value_type INTEGER NOT NULL)",
         "Error creating blackboard_attribute_types table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_module_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)",
+        "Error creating ingest_module_types table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_job_status_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)",
+        "Error creating ingest_job_status_types table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_modules (ingest_module_id INTEGER PRIMARY KEY, display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id));",
+        "Error creating ingest_modules table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_jobs (ingest_job_id INTEGER PRIMARY KEY, obj_id INTEGER NOT NULL, host_name TEXT NOT NULL, start_date_time INTEGER NOT NULL, end_date_time INTEGER NOT NULL, status_id INTEGER NOT NULL, settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id));",
+        "Error creating ingest_jobs table: %s\n")
+		||
+		attempt_exec
+        ("CREATE TABLE ingest_job_modules (ingest_job_id INTEGER, ingest_module_id INTEGER, pipeline_position INTEGER, PRIMARY KEY(ingest_job_id, ingest_module_id), FOREIGN KEY(ingest_job_id) REFERENCES ingest_jobs(ingest_job_id), FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id));",
+        "Error creating ingest_job_modules table: %s\n")
         ||
         attempt_exec
         ("CREATE TABLE reports (report_id INTEGER PRIMARY KEY, path TEXT NOT NULL, crtime INTEGER NOT NULL, src_module_name TEXT NOT NULL, report_name TEXT NOT NULL)",
diff --git a/tsk/auto/guid.cpp b/tsk/auto/guid.cpp
index 3e1f029cb26a12bbe775fb6cc4e989d96c803488..1525f7802f34a481ba263126369ec06217299d50 100755
--- a/tsk/auto/guid.cpp
+++ b/tsk/auto/guid.cpp
@@ -124,7 +124,7 @@ Guid::Guid(const string &fromString)
     else
     {
       charTwo = ch;
-      auto byte = hexPairToChar(charOne, charTwo);
+      unsigned char byte = hexPairToChar(charOne, charTwo);
       _bytes.push_back(byte);
       lookingForFirstChar = true;
     }
@@ -178,8 +178,8 @@ Guid GuidGenerator::newGuid()
 #ifdef GUID_CFUUID
 Guid GuidGenerator::newGuid()
 {
-  auto newId = CFUUIDCreate(NULL);
-  auto bytes = CFUUIDGetUUIDBytes(newId);
+  CFUUIDRef newId = CFUUIDCreate(NULL);
+  CFUUIDBytes bytes = CFUUIDGetUUIDBytes(newId);
   CFRelease(newId);
 
   const unsigned char byteArray[16] =
diff --git a/tsk/fs/exfatfs_dent.c b/tsk/fs/exfatfs_dent.c
index 11c6ce8904de0418688656a41a8bcce0204536e1..789a3f6e124680df9fe74976e686ba2650f32d33 100755
--- a/tsk/fs/exfatfs_dent.c
+++ b/tsk/fs/exfatfs_dent.c
@@ -401,10 +401,6 @@ exfats_parse_special_file_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY
     assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
     assert(a_name_info->fs_dir != NULL);
     assert(a_dentry != NULL);
-    assert(exfatfs_get_enum_from_type(a_dentry->data[0]) == EXFATFS_DIR_ENTRY_TYPE_VOLUME_GUID ||
-           exfatfs_get_enum_from_type(a_dentry->data[0]) == EXFATFS_DIR_ENTRY_TYPE_ALLOC_BITMAP ||
-           exfatfs_get_enum_from_type(a_dentry->data[0]) == EXFATFS_DIR_ENTRY_TYPE_UPCASE_TABLE ||
-           exfatfs_get_enum_from_type(a_dentry->data[0]) == EXFATFS_DIR_ENTRY_TYPE_ACT);
     assert(fatfs_inum_is_in_range(a_name_info->fatfs, a_inum));
 
     /* Starting parse of a new name, save the previous name, if any. */
@@ -441,6 +437,8 @@ exfats_parse_special_file_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY
         case EXFATFS_DIR_ENTRY_TYPE_FILE:
         case EXFATFS_DIR_ENTRY_TYPE_FILE_STREAM:
         case EXFATFS_DIR_ENTRY_TYPE_FILE_NAME:
+        default:
+            a_name_info->fs_name->name[0] = '\0';
             break;
     }