<commit_before>/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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.odk.collect.android.provider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.database.ItemsetDbAdapter;
import org.odk.collect.android.database.ODKSQLiteOpenHelper;
import org.odk.collect.android.provider.FormsProviderAPI.FormsColumns;
import org.odk.collect.android.utilities.FileUtils;
import org.odk.collect.android.utilities.MediaUtils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;

/**
 *
 */
public class FormsProvider extends ContentProvider {

	private static final String t = "FormsProvider";

	private static final String DATABASE_NAME = "forms.db";
	private static final int DATABASE_VERSION = 4;
	private static final String FORMS_TABLE_NAME = "forms";

	private static HashMap<String, String> sFormsProjectionMap;

	private static final int FORMS = 1;
	private static final int FORM_ID = 2;

	private static final UriMatcher sUriMatcher;

	/**
	 * This class helps open, create, and upgrade the database file.
	 */
	private static class DatabaseHelper extends ODKSQLiteOpenHelper {
		// These exist in database versions 2 and 3, but not in 4...
		private static final String TEMP_FORMS_TABLE_NAME = "forms_v4";
		private static final String MODEL_VERSION = "modelVersion";

		DatabaseHelper(String databaseName) {
			super(Collect.getInstance().getMetadataPath(), databaseName, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			onCreateNamed(db, FORMS_TABLE_NAME);
		}

		private void onCreateNamed(SQLiteDatabase db, String tableName) {
			db.execSQL("CREATE TABLE " + tableName + " (" + FormsColumns._ID
					+ " integer primary key, " + FormsColumns.DISPLAY_NAME
					+ " text not null, " + FormsColumns.DISPLAY_SUBTEXT
					+ " text not null, " + FormsColumns.DESCRIPTION
					+ " text, "
					+ FormsColumns.JR_FORM_ID
					+ " text not null, "
					+ FormsColumns.JR_VERSION
					+ " text, "
					+ FormsColumns.MD5_HASH
					+ " text not null, "
					+ FormsColumns.DATE
					+ " integer not null, " // milliseconds
					+ FormsColumns.FORM_MEDIA_PATH + " text not null, "
					+ FormsColumns.FORM_FILE_PATH + " text not null, "
					+ FormsColumns.LANGUAGE + " text, "
					+ FormsColumns.SUBMISSION_URI + " text, "
					+ FormsColumns.BASE64_RSA_PUBLIC_KEY + " text, "
					+ FormsColumns.JRCACHE_FILE_PATH + " text not null );");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			int initialVersion = oldVersion;
			if (oldVersion < 2) {
				Log.w(t, "Upgrading database from version " + oldVersion
						+ " to " + newVersion
						+ ", which will destroy all old data");
				db.execSQL("DROP TABLE IF EXISTS " + FORMS_TABLE_NAME);
				onCreate(db);
				return;
			} else {
				// adding BASE64_RSA_PUBLIC_KEY and changing type and name of
				// integer MODEL_VERSION to text VERSION
				db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);
				onCreateNamed(db, TEMP_FORMS_TABLE_NAME);
				db.execSQL("INSERT INTO "
						+ TEMP_FORMS_TABLE_NAME
						+ " ("
						+ FormsColumns._ID
						+ ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH
						+ ", "
						+ FormsColumns.FORM_FILE_PATH
						+ ", "
						+ FormsColumns.LANGUAGE
						+ ", "
						+ FormsColumns.SUBMISSION_URI
						+ ", "
						+ FormsColumns.JR_VERSION
						+ ", "
						+ ((oldVersion != 3) ? ""
								: (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
						+ FormsColumns.JRCACHE_FILE_PATH
						+ ") SELECT "
						+ FormsColumns._ID
						+ ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH
						+ ", "
						+ FormsColumns.FORM_FILE_PATH
						+ ", "
						+ FormsColumns.LANGUAGE
						+ ", "
						+ FormsColumns.SUBMISSION_URI
						+ ", "
						+ "CASE WHEN "
						+ MODEL_VERSION
						+ " IS NOT NULL THEN "
						+ "CAST("
						+ MODEL_VERSION
						+ " AS TEXT) ELSE NULL END, "
						+ ((oldVersion != 3) ? ""
								: (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
						+ FormsColumns.JRCACHE_FILE_PATH + " FROM "
						+ FORMS_TABLE_NAME);

				// risky failures here...
				db.execSQL("DROP TABLE IF EXISTS " + FORMS_TABLE_NAME);
				onCreateNamed(db, FORMS_TABLE_NAME);
				db.execSQL("INSERT INTO "
						+ FORMS_TABLE_NAME
						+ " ("
						+ FormsColumns._ID
						+ ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH + ", "
						+ FormsColumns.FORM_FILE_PATH + ", "
						+ FormsColumns.LANGUAGE + ", "
						+ FormsColumns.SUBMISSION_URI + ", "
						+ FormsColumns.JR_VERSION + ", "
						+ FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
						+ FormsColumns.JRCACHE_FILE_PATH + ") SELECT "
						+ FormsColumns._ID + ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH + ", "
						+ FormsColumns.FORM_FILE_PATH + ", "
						+ FormsColumns.LANGUAGE + ", "
						+ FormsColumns.SUBMISSION_URI + ", "
						+ FormsColumns.JR_VERSION + ", "
						+ FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
						+ FormsColumns.JRCACHE_FILE_PATH + " FROM "
						+ TEMP_FORMS_TABLE_NAME);
				db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);

				Log.w(t, "Successfully upgraded database from version "
						+ initialVersion + " to " + newVersion
						+ ", without destroying all the old data");
			}
		}
	}

	private DatabaseHelper mDbHelper;

    private DatabaseHelper getDbHelper() {
        // wrapper to test and reset/set the dbHelper based upon the attachment state of the device.
        try {
            Collect.getInstance().createODKDirs();
        } catch (RuntimeException e) {
        	mDbHelper = null;
            return null;
        }

        if (mDbHelper != null) {
        	return mDbHelper;
        }
        mDbHelper = new DatabaseHelper(DATABASE_NAME);
        return mDbHelper;
    }

	@Override
	public boolean onCreate() {
        // must be at the beginning of any activity that can be called from an external intent
		DatabaseHelper h = getDbHelper();
        return h != null;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
		qb.setTables(FORMS_TABLE_NAME);

		switch (sUriMatcher.match(uri)) {
		case FORMS:
			qb.setProjectionMap(sFormsProjectionMap);
			break;

		case FORM_ID:
			qb.setProjectionMap(sFormsProjectionMap);
			qb.appendWhere(FormsColumns._ID + "="
					+ uri.getPathSegments().get(1));
			break;

		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		// Get the database and run the query
		SQLiteDatabase db = getDbHelper().getReadableDatabase();
		Cursor c = qb.query(db, projection, selection, selectionArgs, null,
				null, sortOrder);

		// Tell the cursor what uri to watch, so it knows when its source data
		// changes
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	@Override
	public String getType(Uri uri) {
		switch (sUriMatcher.match(uri)) {
		case FORMS:
			return FormsColumns.CONTENT_TYPE;

		case FORM_ID:
			return FormsColumns.CONTENT_ITEM_TYPE;

		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}
	}

	@Override
	public synchronized Uri insert(Uri uri, ContentValues initialValues) {
		// Validate the requested uri
		if (sUriMatcher.match(uri) != FORMS) {
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		ContentValues values;
		if (initialValues != null) {
            values = new ContentValues(initialValues);
		} else {
			values = new ContentValues();
		}

		if (!values.containsKey(FormsColumns.FORM_FILE_PATH)) {
			throw new IllegalArgumentException(FormsColumns.FORM_FILE_PATH
					+ " must be specified.");
		}

		// Normalize the file path.
		// (don't trust the requester).
		String filePath = values.getAsString(FormsColumns.FORM_FILE_PATH);
		File form = new File(filePath);
		filePath = form.getAbsolutePath(); // normalized
		values.put(FormsColumns.FORM_FILE_PATH, filePath);

		Long now = System.currentTimeMillis();

        // Decide if we should do an insert or replace.
        boolean replace = false;
        if (values.containsKey(FormsProviderAPI.SQL_INSERT_OR_REPLACE)) {
            replace = values.getAsBoolean(FormsProviderAPI.SQL_INSERT_OR_REPLACE);

            // Remove the key, so we don't pass that on to db.insert() or db.replace()
            values.remove(FormsProviderAPI.SQL_INSERT_OR_REPLACE);
        }

        // Make sure that the necessary fields are all set
		if (!values.containsKey(FormsColumns.DATE)) {
			values.put(FormsColumns.DATE, now);
		}

		if (!values.containsKey(FormsColumns.DISPLAY_SUBTEXT)) {
			Date today = new Date();
			String ts = new SimpleDateFormat(getContext().getString(
					R.string.added_on_date_at_time), Locale.getDefault())
					.format(today);
			values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
		}

		if (!values.containsKey(FormsColumns.DISPLAY_NAME)) {
			values.put(FormsColumns.DISPLAY_NAME, form.getName());
		}

		// don't let users put in a manual md5 hash
		if (values.containsKey(FormsColumns.MD5_HASH)) {
			values.remove(FormsColumns.MD5_HASH);
		}
		String md5 = FileUtils.getMd5Hash(form);
		values.put(FormsColumns.MD5_HASH, md5);

		if (!values.containsKey(FormsColumns.JRCACHE_FILE_PATH)) {
			String cachePath = Collect.getInstance().getCachePath() + File.separator + md5
					+ ".formdef";
			values.put(FormsColumns.JRCACHE_FILE_PATH, cachePath);
		}
		if (!values.containsKey(FormsColumns.FORM_MEDIA_PATH)) {
			String pathNoExtension = filePath.substring(0,
					filePath.lastIndexOf("."));
			String mediaPath = pathNoExtension + "-media";
			values.put(FormsColumns.FORM_MEDIA_PATH, mediaPath);
		}

		SQLiteDatabase db = getDbHelper().getWritableDatabase();

        long rowId;
        if (replace) {
            rowId = db.replace(FORMS_TABLE_NAME, null, values);
        } else {
            // first try to see if a record with this filename already exists...
            String[] projection = {FormsColumns._ID, FormsColumns.FORM_FILE_PATH};
            String[] selectionArgs = {filePath};
            String selection = FormsColumns.FORM_FILE_PATH + "=?";
            Cursor c = null;
            try {
                c = db.query(FORMS_TABLE_NAME, projection, selection,
                        selectionArgs, null, null, null);
                if (c.getCount() > 0) {
                    // already exists
                    throw new SQLException("FAILED Insert into " + uri
                            + " -- row already exists for form definition file: "
                            + filePath);
                }
            } finally {
                if (c != null) {
                    c.close();
                }
            }
            rowId = db.insert(FORMS_TABLE_NAME, null, values);
        }<commit_msg>Try and fix the multiple form insert bug.<commit_after>/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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.odk.collect.android.provider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.database.ItemsetDbAdapter;
import org.odk.collect.android.database.ODKSQLiteOpenHelper;
import org.odk.collect.android.provider.FormsProviderAPI.FormsColumns;
import org.odk.collect.android.utilities.FileUtils;
import org.odk.collect.android.utilities.MediaUtils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;

/**
 *
 */
public class FormsProvider extends ContentProvider {

	private static final String t = "FormsProvider";

	private static final String DATABASE_NAME = "forms.db";
	private static final int DATABASE_VERSION = 4;
	private static final String FORMS_TABLE_NAME = "forms";

	private static HashMap<String, String> sFormsProjectionMap;

	private static final int FORMS = 1;
	private static final int FORM_ID = 2;

	private static final UriMatcher sUriMatcher;

	/**
	 * This class helps open, create, and upgrade the database file.
	 */
	private static class DatabaseHelper extends ODKSQLiteOpenHelper {
		// These exist in database versions 2 and 3, but not in 4...
		private static final String TEMP_FORMS_TABLE_NAME = "forms_v4";
		private static final String MODEL_VERSION = "modelVersion";

		DatabaseHelper(String databaseName) {
			super(Collect.getInstance().getMetadataPath(), databaseName, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			onCreateNamed(db, FORMS_TABLE_NAME);
		}

		private void onCreateNamed(SQLiteDatabase db, String tableName) {
			db.execSQL("CREATE TABLE " + tableName + " (" + FormsColumns._ID
					+ " integer primary key, " + FormsColumns.DISPLAY_NAME
					+ " text not null, " + FormsColumns.DISPLAY_SUBTEXT
					+ " text not null, " + FormsColumns.DESCRIPTION
					+ " text, "
					+ FormsColumns.JR_FORM_ID
					+ " text not null, "
					+ FormsColumns.JR_VERSION
					+ " text, "
					+ FormsColumns.MD5_HASH
					+ " text not null, "
					+ FormsColumns.DATE
					+ " integer not null, " // milliseconds
					+ FormsColumns.FORM_MEDIA_PATH + " text not null, "
					+ FormsColumns.FORM_FILE_PATH + " text not null, "
					+ FormsColumns.LANGUAGE + " text, "
					+ FormsColumns.SUBMISSION_URI + " text, "
					+ FormsColumns.BASE64_RSA_PUBLIC_KEY + " text, "
					+ FormsColumns.JRCACHE_FILE_PATH + " text not null );");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			int initialVersion = oldVersion;
			if (oldVersion < 2) {
				Log.w(t, "Upgrading database from version " + oldVersion
						+ " to " + newVersion
						+ ", which will destroy all old data");
				db.execSQL("DROP TABLE IF EXISTS " + FORMS_TABLE_NAME);
				onCreate(db);
				return;
			} else {
				// adding BASE64_RSA_PUBLIC_KEY and changing type and name of
				// integer MODEL_VERSION to text VERSION
				db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);
				onCreateNamed(db, TEMP_FORMS_TABLE_NAME);
				db.execSQL("INSERT INTO "
						+ TEMP_FORMS_TABLE_NAME
						+ " ("
						+ FormsColumns._ID
						+ ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH
						+ ", "
						+ FormsColumns.FORM_FILE_PATH
						+ ", "
						+ FormsColumns.LANGUAGE
						+ ", "
						+ FormsColumns.SUBMISSION_URI
						+ ", "
						+ FormsColumns.JR_VERSION
						+ ", "
						+ ((oldVersion != 3) ? ""
								: (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
						+ FormsColumns.JRCACHE_FILE_PATH
						+ ") SELECT "
						+ FormsColumns._ID
						+ ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH
						+ ", "
						+ FormsColumns.FORM_FILE_PATH
						+ ", "
						+ FormsColumns.LANGUAGE
						+ ", "
						+ FormsColumns.SUBMISSION_URI
						+ ", "
						+ "CASE WHEN "
						+ MODEL_VERSION
						+ " IS NOT NULL THEN "
						+ "CAST("
						+ MODEL_VERSION
						+ " AS TEXT) ELSE NULL END, "
						+ ((oldVersion != 3) ? ""
								: (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
						+ FormsColumns.JRCACHE_FILE_PATH + " FROM "
						+ FORMS_TABLE_NAME);

				// risky failures here...
				db.execSQL("DROP TABLE IF EXISTS " + FORMS_TABLE_NAME);
				onCreateNamed(db, FORMS_TABLE_NAME);
				db.execSQL("INSERT INTO "
						+ FORMS_TABLE_NAME
						+ " ("
						+ FormsColumns._ID
						+ ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH + ", "
						+ FormsColumns.FORM_FILE_PATH + ", "
						+ FormsColumns.LANGUAGE + ", "
						+ FormsColumns.SUBMISSION_URI + ", "
						+ FormsColumns.JR_VERSION + ", "
						+ FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
						+ FormsColumns.JRCACHE_FILE_PATH + ") SELECT "
						+ FormsColumns._ID + ", "
						+ FormsColumns.DISPLAY_NAME
						+ ", "
						+ FormsColumns.DISPLAY_SUBTEXT
						+ ", "
						+ FormsColumns.DESCRIPTION
						+ ", "
						+ FormsColumns.JR_FORM_ID
						+ ", "
						+ FormsColumns.MD5_HASH
						+ ", "
						+ FormsColumns.DATE
						+ ", " // milliseconds
						+ FormsColumns.FORM_MEDIA_PATH + ", "
						+ FormsColumns.FORM_FILE_PATH + ", "
						+ FormsColumns.LANGUAGE + ", "
						+ FormsColumns.SUBMISSION_URI + ", "
						+ FormsColumns.JR_VERSION + ", "
						+ FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
						+ FormsColumns.JRCACHE_FILE_PATH + " FROM "
						+ TEMP_FORMS_TABLE_NAME);
				db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);

				Log.w(t, "Successfully upgraded database from version "
						+ initialVersion + " to " + newVersion
						+ ", without destroying all the old data");
			}
		}
	}

	private DatabaseHelper mDbHelper;

    private DatabaseHelper getDbHelper() {
        // wrapper to test and reset/set the dbHelper based upon the attachment state of the device.
        try {
            Collect.getInstance().createODKDirs();
        } catch (RuntimeException e) {
        	mDbHelper = null;
            return null;
        }

        if (mDbHelper != null) {
        	return mDbHelper;
        }
        mDbHelper = new DatabaseHelper(DATABASE_NAME);
        return mDbHelper;
    }

	@Override
	public boolean onCreate() {
        // must be at the beginning of any activity that can be called from an external intent
		DatabaseHelper h = getDbHelper();
        return h != null;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
		qb.setTables(FORMS_TABLE_NAME);

		switch (sUriMatcher.match(uri)) {
		case FORMS:
			qb.setProjectionMap(sFormsProjectionMap);
			break;

		case FORM_ID:
			qb.setProjectionMap(sFormsProjectionMap);
			qb.appendWhere(FormsColumns._ID + "="
					+ uri.getPathSegments().get(1));
			break;

		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		// Get the database and run the query
		SQLiteDatabase db = getDbHelper().getReadableDatabase();
		Cursor c = qb.query(db, projection, selection, selectionArgs, null,
				null, sortOrder);

		// Tell the cursor what uri to watch, so it knows when its source data
		// changes
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	@Override
	public String getType(Uri uri) {
		switch (sUriMatcher.match(uri)) {
		case FORMS:
			return FormsColumns.CONTENT_TYPE;

		case FORM_ID:
			return FormsColumns.CONTENT_ITEM_TYPE;

		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}
	}

	@Override
	public synchronized Uri insert(Uri uri, ContentValues initialValues) {
		// Validate the requested uri
		if (sUriMatcher.match(uri) != FORMS) {
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		ContentValues values;
		if (initialValues != null) {
            values = new ContentValues(initialValues);
		} else {
			values = new ContentValues();
		}

		if (!values.containsKey(FormsColumns.FORM_FILE_PATH)) {
			throw new IllegalArgumentException(FormsColumns.FORM_FILE_PATH
					+ " must be specified.");
		}

		// Normalize the file path.
		// (don't trust the requester).
		String filePath = values.getAsString(FormsColumns.FORM_FILE_PATH);
		File form = new File(filePath);
		filePath = form.getAbsolutePath(); // normalized
		values.put(FormsColumns.FORM_FILE_PATH, filePath);

		Long now = System.currentTimeMillis();

        // Decide if we should do an insert or replace.
        boolean replace = false;
        if (values.containsKey(FormsProviderAPI.SQL_INSERT_OR_REPLACE)) {
            replace = values.getAsBoolean(FormsProviderAPI.SQL_INSERT_OR_REPLACE);

            // Remove the key, so we don't pass that on to db.insert() or db.replace()
            values.remove(FormsProviderAPI.SQL_INSERT_OR_REPLACE);
        }

        // Make sure that the necessary fields are all set
		if (!values.containsKey(FormsColumns.DATE)) {
			values.put(FormsColumns.DATE, now);
		}

		if (!values.containsKey(FormsColumns.DISPLAY_SUBTEXT)) {
			Date today = new Date();
			String ts = new SimpleDateFormat(getContext().getString(
					R.string.added_on_date_at_time), Locale.getDefault())
					.format(today);
			values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
		}

		if (!values.containsKey(FormsColumns.DISPLAY_NAME)) {
			values.put(FormsColumns.DISPLAY_NAME, form.getName());
		}

		// don't let users put in a manual md5 hash
		if (values.containsKey(FormsColumns.MD5_HASH)) {
			values.remove(FormsColumns.MD5_HASH);
		}
		String md5 = FileUtils.getMd5Hash(form);
		values.put(FormsColumns.MD5_HASH, md5);

		if (!values.containsKey(FormsColumns.JRCACHE_FILE_PATH)) {
			String cachePath = Collect.getInstance().getCachePath() + File.separator + md5
					+ ".formdef";
			values.put(FormsColumns.JRCACHE_FILE_PATH, cachePath);
		}
		if (!values.containsKey(FormsColumns.FORM_MEDIA_PATH)) {
			String pathNoExtension = filePath.substring(0,
					filePath.lastIndexOf("."));
			String mediaPath = pathNoExtension + "-media";
			values.put(FormsColumns.FORM_MEDIA_PATH, mediaPath);
		}

		SQLiteDatabase db = getDbHelper().getWritableDatabase();

        long rowId;
        if (replace) {
            // If we want this insert to always replace, then do a transactional delete followed
            // by an insert. The primary key is _ID, which we can't know in advance.
            try {
                db.beginTransaction();
                db.delete(FORMS_TABLE_NAME, FormsColumns.FORM_FILE_PATH + "=?", new String[]{filePath});
                rowId = db.insert(FORMS_TABLE_NAME, null, values);
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        } else {
            // first try to see if a record with this filename already exists...
            String[] projection = {FormsColumns._ID, FormsColumns.FORM_FILE_PATH};
            String[] selectionArgs = {filePath};
            String selection = FormsColumns.FORM_FILE_PATH + "=?";
            Cursor c = null;
            try {
                c = db.query(FORMS_TABLE_NAME, projection, selection,
                        selectionArgs, null, null, null);
                if (c.getCount() > 0) {
                    // already exists
                    throw new SQLException("FAILED Insert into " + uri
                            + " -- row already exists for form definition file: "
                            + filePath);
                }
            } finally {
                if (c != null) {
                    c.close();
                }
            }
            rowId = db.insert(FORMS_TABLE_NAME, null, values);
        }
{'fraction_non_alphanumeric': 0.10701176826730238, 'fraction_numerical': 0.0034812880765883376, 'mean_word_length': 2.723826969141891, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3143111', 'n_tokens_mistral': 9905, 'n_tokens_neox': 7897, 'n_words': 1784}