mobile/android/base/db/GeckoProvider.java.in
author Wes Johnston <wjohnston@mozilla.com>
Fri, 13 Apr 2012 18:38:11 -0700
changeset 91653 1dde63bcf17e5e6a1cd5737b03ea71da63d94ee3
parent 91260 f0f385e9f904ab2f5605ad646bcc41e55ae72a88
child 94865 01f48ef1ee9232fecd075dc1d688b4f9a1c3516f
permissions -rw-r--r--
Bug 745383 - Use database path for hashtables. r=mfinkle, a=mfinkle

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#filter substitution
package @ANDROID_PACKAGE_NAME@.db;

import java.io.File;
import java.io.IOException;
import java.lang.IllegalArgumentException;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;

import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoEventListener;
import org.mozilla.gecko.db.BrowserContract.CommonColumns;
import org.mozilla.gecko.db.DBUtils;
import org.mozilla.gecko.db.BrowserContract.Passwords;
import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sqlite.SQLiteBridge;
import org.mozilla.gecko.sqlite.SQLiteBridgeException;
import org.mozilla.gecko.sync.Utils;

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

/*
 * Provides a basic ContentProvider that sets up and sends queries through
 * SQLiteBridge. Content providers should extend this by setting the appropriate
 * table and version numbers in onCreate, and implementing the abstract methods:
 *
 *  public abstract String getTable(Uri uri);
 *  public abstract String getSortOrder(Uri uri, String aRequested);
 *  public abstract void setupDefaults(Uri uri, ContentValues values);
 *  public abstract void initGecko();
 */

public abstract class GeckoProvider extends ContentProvider {
    private String mLogTag = "GeckoPasswordsProvider";
    private String mDBName = "";
    private int mDBVersion = 0;
    private HashMap<String, SQLiteBridge> mDatabasePerProfile;
    protected Context mContext = null;

    @Override
    public void shutdown() {
        if (mDatabasePerProfile == null)
          return;

        Collection<SQLiteBridge> bridges = mDatabasePerProfile.values();
        Iterator<SQLiteBridge> it = bridges.iterator();

        while (it.hasNext()) {
            SQLiteBridge bridge = it.next();
            if (bridge != null) {
                try {
                    bridge.close();
                } catch (Exception ex) { }
            }
        }

        mDatabasePerProfile = null;
    }

    public void finalize() {
        shutdown();
    }

    protected void setLogTag(String aLogTag) {
        mLogTag = aLogTag;
    }

    protected String getLogTag() {
        return mLogTag;
    }

    protected void setDBName(String aDBName) {
        mDBName = aDBName;
    }

    protected String getDBName() {
        return mDBName;
    }

    protected void setDBVersion(int aVersion) {
        mDBVersion = aVersion;
    }

    protected int getDBVersion() {
        return mDBVersion;
    }

    private SQLiteBridge getDB(Context context, final String databasePath) {
        SQLiteBridge bridge = null;

        boolean dbNeedsSetup = true;
        try {
            String resourcePath = context.getPackageResourcePath();
            GeckoAppShell.loadSQLiteLibs(context, resourcePath);
            GeckoAppShell.loadNSSLibs(context, resourcePath);
            bridge = SQLiteBridge.openDatabase(databasePath, null, 0);
            int version = bridge.getVersion();
            dbNeedsSetup = version != mDBVersion;
        } catch (SQLiteBridgeException ex) {
            // close the database
            if (bridge != null)
                bridge.close();

            // this will throw if the database can't be found
            // we should attempt to set it up if Gecko is running
            dbNeedsSetup = true;
            Log.e(mLogTag, "Error getting version ", ex);

            // if Gecko is not running, we should bail out. Otherwise we try to
            // let Gecko build the database for us
            if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
                Log.e(mLogTag, "Can not set up database. Gecko is not running");
                return null;
            }
        }

        // If the database is not set up yet, or is the wrong schema version, we send an initialize
        // call to Gecko. Gecko will handle building the database file correctly, as well as any
        // migrations that are necessary
        if (dbNeedsSetup) {
            Log.i(mLogTag, "Sending init to gecko");
            bridge = null;
            initGecko();
        }
        if (bridge != null)
            mDatabasePerProfile.put(databasePath, bridge);

        return bridge;
    }

    private SQLiteBridge getDatabaseForProfile(String profile) {
        if (TextUtils.isEmpty(profile)) {
            Log.d(mLogTag, "No profile provided, using default");
            profile = BrowserContract.DEFAULT_PROFILE;
        }

        SQLiteBridge db = null;
        synchronized (this) {
          String dbPath = getDatabasePathForProfile(profile);
          db = mDatabasePerProfile.get(dbPath);
          if (db == null) {
              db = getDB(mContext, dbPath);
          }
        }

        Log.d(mLogTag, "Successfully created database helper for profile: " + profile);
        return db;
    }

    private SQLiteBridge getDatabaseForPath(String profilePath) {
        SQLiteBridge db = null;
        synchronized (this) {
            db = mDatabasePerProfile.get(profilePath);
            if (db == null) {
                File profileDir = new File(profilePath, mDBName);
                db = getDB(mContext, profileDir.getPath());
            }
        }

        Log.d(mLogTag, "Successfully created database helper for path: " + profilePath);
        return db;
    }

    private String getDatabasePathForProfile(String profile) {
        File profileDir = GeckoProfile.get(mContext, profile).getDir();
        if (profileDir == null) {
            Log.d(mLogTag, "Couldn't find directory for profile: " + profile);
            return null;
        }

        Log.d(mLogTag, "Using path: " + profileDir.getPath());
        String databasePath = new File(profileDir, mDBName).getAbsolutePath();
        return databasePath;
    }

    private SQLiteBridge getDatabase(Uri uri) {
        String profile = null;
        String profilePath = null;

        profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
        profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH);

        // Testing will specify the absolute profile path
        if (profilePath != null)
          return getDatabaseForPath(profilePath);
        return getDatabaseForProfile(profile);
    }

    @Override
    public boolean onCreate() {
        mContext = getContext();
        synchronized (this) {
            mDatabasePerProfile = new HashMap<String, SQLiteBridge>();
        }
        return true;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int deleted = 0;
        final SQLiteBridge db = getDatabase(uri);
        if (db == null)
            return deleted;

        try {
            deleted = db.delete(getTable(uri), selection, selectionArgs);
        } catch (SQLiteBridgeException ex) {
            Log.e(mLogTag, "Error deleting record", ex);
            throw ex;
        }

        return deleted;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long id = -1;
        final SQLiteBridge db = getDatabase(uri);

        // If we can not get a SQLiteBridge instance, its likely that the database
        // has not been set up and Gecko is not running. We return null and expect
        // callers to try again later
        if (db == null)
            return null;

        setupDefaults(uri, values);

        boolean useTransaction = !db.inTransaction();
        try {
            if (useTransaction) {
                db.beginTransaction();
            }
 
            // onPreInsert does a check for the item in the deleted table in some cases
            // so we put it inside this transaction
            onPreInsert(values, uri, db);
            id = db.insert(getTable(uri), null, values);

            if (useTransaction) {
                db.setTransactionSuccessful();
            }
        } catch (SQLiteBridgeException ex) {
            Log.e(mLogTag, "Error inserting in db", ex);
            throw ex;
        } finally {
            if (useTransaction) {
                db.endTransaction();
            }
        }

        return ContentUris.withAppendedId(uri, id);
    }

    @Override
    public int bulkInsert(Uri uri, ContentValues[] allValues) {
        final SQLiteBridge db = getDatabase(uri);
        // If we can not get a SQLiteBridge instance, its likely that the database
        // has not been set up and Gecko is not running. We return 0 and expect
        // callers to try again later
        if (db == null)
            return 0;

        long id = -1;
        int rowsAdded = 0;

        String table = getTable(uri);

        try {
            db.beginTransaction();
            for (ContentValues initialValues : allValues) {
                ContentValues values = new ContentValues(initialValues);
                setupDefaults(uri, values);
                onPreInsert(values, uri, db);
                id = db.insert(table, null, values);
                rowsAdded++;
            }
            db.setTransactionSuccessful();
        } catch (SQLiteBridgeException ex) {
            Log.e(mLogTag, "Error inserting in db", ex);
            throw ex;
        } finally {
            db.endTransaction();
        }
        return rowsAdded;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        int updated = 0;
        final SQLiteBridge db = getDatabase(uri);

        // If we can not get a SQLiteBridge instance, its likely that the database
        // has not been set up and Gecko is not running. We return null and expect
        // callers to try again later
        if (db == null)
            return updated;

        onPreUpdate(values, uri, db);

        try {
            updated = db.update(getTable(uri), values, selection, selectionArgs);
        } catch (SQLiteBridgeException ex) {
            Log.e(mLogTag, "Error updating table", ex);
            throw ex;
        }

        return updated;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        final SQLiteBridge db = getDatabase(uri);

        // If we can not get a SQLiteBridge instance, its likely that the database
        // has not been set up and Gecko is not running. We return null and expect
        // callers to try again later
        if (db == null)
            return cursor;

        sortOrder = getSortOrder(uri, sortOrder);

        try {
            cursor = db.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder, null);
            onPostQuery(cursor, uri, db);
        } catch (SQLiteBridgeException ex) {
            Log.e(mLogTag, "Error querying database", ex);
            throw ex;
        }

        return cursor;
    }

    public abstract String getTable(Uri uri);

    public abstract String getSortOrder(Uri uri, String aRequested);

    public abstract void setupDefaults(Uri uri, ContentValues values);

    public abstract void initGecko();

    public abstract void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db);

    public abstract void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db);

    public abstract void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db);
}