Android Open Source - AnkiStats Anki Db






From Project

Back to project page AnkiStats.

License

The source code is released under:

GNU General Public License

If you think the Android project AnkiStats listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/****************************************************************************************
 * Copyright (c) 2009 Daniel Svrd <daniel.svard@gmail.com>                             *
 * Copyright (c) 2009 Nicolas Raoul <nicolas.raoul@gmail.com>                           *
 * Copyright (c) 2009 Andrew <andrewdubya@gmail.com>                                    *
 * Copyright (c) 2011 Norbert Nagold <norbert.nagold@gmail.com>                         *
 *                                                                                      *
 * This program is free software; you can redistribute it and/or modify it under        *
 * the terms of the GNU General Public License as published by the Free Software        *
 * Foundation; either version 3 of the License, or (at your option) any later           *
 * version.                                                                             *
 *                                                                                      *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
 *                                                                                      *
 * You should have received a copy of the GNU General Public License along with         *
 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/
//from  w  w w  . ja va 2  s. c  om
package com.wildplot.android.ankistats;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import android.widget.Toast;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Database layer for AnkiDroid. Can read the native Anki format through Android's SQLite driver.
 */
public class AnkiDb {

    private static final String[] MOD_SQLS = new String[] { "insert", "update", "delete" };

    /**
     * The deck, which is actually an SQLite database.
     */
    private SQLiteDatabase mDatabase;
    private boolean mMod = false;


    /**
     * Open a database connection to an ".anki" SQLite file.
     */
    public AnkiDb(String ankiFilename) {
        mDatabase = SQLiteDatabase.openDatabase(ankiFilename, null,
                (SQLiteDatabase.OPEN_READWRITE + SQLiteDatabase.CREATE_IF_NECESSARY)
                        | SQLiteDatabase.NO_LOCALIZED_COLLATORS
        );
        if (mDatabase != null) {
            setWalJournalMode();
            mDatabase.rawQuery("PRAGMA synchronous = 2", null);
        }
        // getDatabase().beginTransactionNonExclusive();
        mMod = false;
    }


    /**
     * Closes a previously opened database connection.
     */
    public void closeDatabase() {
        if (mDatabase != null) {
            // set journal mode again to delete in order to make the db accessible for anki desktop and for full upload
            setDeleteJournalMode();
            mDatabase.close();
            Log.i(AnkiStatsApplication.TAG, "AnkiDb - closeDatabase, database " + mDatabase.getPath() + " closed = "
                    + !mDatabase.isOpen());
            mDatabase = null;
        }
    }


    public void commit() {
        // SQLiteDatabase db = getDatabase();
        // while (db.inTransaction()) {
        // db.setTransactionSuccessful();
        // db.endTransaction();
        // }
        // db.beginTransactionNonExclusive();
    }


    public SQLiteDatabase getDatabase() {
        return mDatabase;
    }


    public void setMod(boolean mod) {
        mMod = mod;
    }


    public boolean getMod() {
        return mMod;
    }


    /**
     * Convenience method for querying the database for a single integer result.
     *
     * @param query The raw SQL query to use.
     * @return The integer result of the query.
     */
    public int queryScalar(String query) throws SQLException {
        return queryScalar(query, true);
    }


    public int queryScalar(String query, boolean throwException) throws SQLException {
        Cursor cursor = null;
        int scalar;
        try {
            cursor = mDatabase.rawQuery(query, null);
            if (!cursor.moveToNext()) {
                if (throwException) {
                    throw new SQLException("No result for query: " + query);
                } else {
                    return 0;
                }
            }

            scalar = cursor.getInt(0);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }

        return scalar;
    }


    public String queryString(String query) throws SQLException {
        Cursor cursor = null;
        try {
            cursor = mDatabase.rawQuery(query, null);
            if (!cursor.moveToNext()) {
                throw new SQLException("No result for query: " + query);
            }
            return cursor.getString(0);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }


    public long queryLongScalar(String query) throws SQLException {
        return queryLongScalar(query, true);
    }


    public long queryLongScalar(String query, boolean throwException) throws SQLException {
        Cursor cursor = null;
        long scalar;
        try {
            cursor = mDatabase.rawQuery(query, null);
            if (!cursor.moveToNext()) {
                if (throwException) {
                    throw new SQLException("No result for query: " + query);
                } else {
                    return 0;
                }
            }
            scalar = cursor.getLong(0);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }

        return scalar;
    }


    /**
     * Convenience method for querying the database for an entire column. The column will be returned as an ArrayList of
     * the specified class. See Deck.initUndo() for a usage example.
     *
     * @param type The class of the column's data type. Example: int.class, String.class.
     * @param query The SQL query statement.
     * @param column The column id in the result set to return.
     * @return An ArrayList with the contents of the specified column.
     */
    public <T> ArrayList<T> queryColumn(Class<T> type, String query, int column) {
        int nullExceptionCount = 0;
        InvocationTargetException nullException = null; // to catch the null exception for reporting
        ArrayList<T> results = new ArrayList<T>();
        Cursor cursor = null;

        try {
            cursor = mDatabase.rawQuery(query, null);
            String methodName = getCursorMethodName(type.getSimpleName());
            while (cursor.moveToNext()) {
                try {
                    // The magical line. Almost as illegible as python code ;)
                    results.add(type.cast(Cursor.class.getMethod(methodName, int.class).invoke(cursor, column)));
                } catch (InvocationTargetException e) {
                    if (cursor.isNull(column)) { // null value encountered
                        nullExceptionCount++;
                        if (nullExceptionCount == 1) { // Toast and error report first time only
                            nullException = e;
                            Toast.makeText(AnkiStatsApplication.getInstance().getBaseContext(),
                                    "Error report pending: unexpected null in database.", Toast.LENGTH_LONG).show();
                        }
                        continue; // attempt to skip this null record
                    } else {
                        throw new RuntimeException(e);
                    }
                }
            }
        } catch (NoSuchMethodException e) {
            // This is really coding error, so it should be revealed if it ever happens
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            // This is really coding error, so it should be revealed if it ever happens
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // This is really coding error, so it should be revealed if it ever happens
            throw new RuntimeException(e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            if (nullExceptionCount > 0) {
                if (nullException != null) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("AnkiDb.queryColumn (column " + column + "): ");
                    sb.append("Exception due to null. Query: " + query);
                    sb.append(" Null occurrences during this query: " + nullExceptionCount);
                    //AnkiDroidApp.saveExceptionReportFile(nullException, "queryColumn_encounteredNull", sb.toString());
                    Log.w(AnkiStatsApplication.TAG, sb.toString());
                } else { // nullException not properly initialized
                    StringBuilder sb = new StringBuilder();
                    sb.append("AnkiDb.queryColumn(): Critical error -- ");
                    sb.append("unable to pass in the actual exception to error reporting.");
                    //AnkiDroidApp.saveExceptionReportFile("queryColumn_encounteredNull", sb.toString());
                    Log.e(AnkiStatsApplication.TAG, sb.toString());
                }
            }
        }

        return results;
    }


    /**
     * Mapping of Java type names to the corresponding Cursor.get method.
     *
     * @param typeName The simple name of the type's class. Example: String.class.getSimpleName().
     * @return The name of the Cursor method to be called.
     */
    private static String getCursorMethodName(String typeName) {
        if (typeName.equals("String")) {
            return "getString";
        } else if (typeName.equals("Long")) {
            return "getLong";
        } else if (typeName.equals("Integer")) {
            return "getInt";
        } else if (typeName.equals("Float")) {
            return "getFloat";
        } else if (typeName.equals("Double")) {
            return "getDouble";
        } else {
            return null;
        }
    }


    public void execute(String sql) {
        execute(sql, null);
    }


    public void execute(String sql, Object[] object) {
        String s = sql.trim().toLowerCase(Locale.US);
        // mark modified?
        for (String mo : MOD_SQLS) {
            if (s.startsWith(mo)) {
                mMod = true;
                break;
            }
        }
        if (object == null) {
            this.getDatabase().execSQL(sql);
        } else {
            this.getDatabase().execSQL(sql, object);
        }
    }


    /** update must always be called via AnkiDb in order to mark the db as changed */
    public int update(String table, ContentValues values) {
        return update(table, values, null, null);
    }


    /** update must always be called via AnkiDb in order to mark the db as changed */
    public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
        mMod = true;
        return getDatabase().update(table, values, whereClause, whereArgs);
    }


    /** insert must always be called via AnkiDb in order to mark the db as changed */
    public long insert(String table, String nullColumnHack, ContentValues values) {
        mMod = true;
        return getDatabase().insert(table, nullColumnHack, values);
    }


    public void executeMany(String sql, List<Object[]> list) {
        mMod = true;
        mDatabase.beginTransaction();
        try {
            for (Object[] o : list) {
                mDatabase.execSQL(sql, o);
            }
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }
    }


    /**
     * Anki desktop only uses a journal_mode of DELETE. We therefore set this before database closure to ensure
     * compatibility. This allows full upload to AnkiWeb and manual collection migration, but has no bearing on partial
     * progress syncs.
     */
    protected void setDeleteJournalMode() {
        disableWriteAheadLogging();
        queryString("PRAGMA journal_mode = DELETE");
    }


    /** to be called for each database upon opening */
    private void setWalJournalMode() {
        if (AnkiStatsApplication.SDK_VERSION >= 11) {
            queryString("PRAGMA journal_mode = WAL");
        } else {
            setDeleteJournalMode();
        }
    }


    /**
     * Attempts to disable write ahead logging using a method on the {@link android.database.sqlite.SQLiteDatabase} object.
     * <p>
     * The method might not exist (it is only included in API level 16) but we attempt anyway since it is part of
     * present in a number of implementations.
     */
    private void disableWriteAheadLogging() {
        SQLiteDatabase db = getDatabase();
        // The call to disableWriteAheadLogging() below requires no transaction is in progress.
        if (db.inTransaction()) {
            db.endTransaction();
        }
        AnkiStatsApplication.disableDatabaseWriteAheadLogging(db);
    }

}




Java Source Code List

com.wildplot.android.ankistats.AnkiDb.java
com.wildplot.android.ankistats.AnkiStatsActivity.java
com.wildplot.android.ankistats.AnkiStatsApplication.java
com.wildplot.android.ankistats.AnswerButton.java
com.wildplot.android.ankistats.ApplicationTest.java
com.wildplot.android.ankistats.CardsTypes.java
com.wildplot.android.ankistats.CollectionData.java
com.wildplot.android.ankistats.Forecast.java
com.wildplot.android.ankistats.HourlyBreakdown.java
com.wildplot.android.ankistats.Intervals.java
com.wildplot.android.ankistats.ReviewCount.java
com.wildplot.android.ankistats.Utils.java
com.wildplot.android.ankistats.WeeklyBreakdown.java
com.wildplot.android.parsing.Atom.java
com.wildplot.android.parsing.ExpressionFormatException.java
com.wildplot.android.parsing.Expression.java
com.wildplot.android.parsing.Factor.java
com.wildplot.android.parsing.Pow.java
com.wildplot.android.parsing.Term.java
com.wildplot.android.parsing.TopLevelParser.java
com.wildplot.android.parsing.TreeElement.java
com.wildplot.android.parsing.AtomTypes.FunctionXAtom.java
com.wildplot.android.parsing.AtomTypes.FunctionXYAtom.java
com.wildplot.android.parsing.AtomTypes.MathFunctionAtom.java
com.wildplot.android.parsing.AtomTypes.NumberAtom.java
com.wildplot.android.parsing.AtomTypes.VariableAtom.java
com.wildplot.android.parsing.AtomTypes.XVariableAtom.java
com.wildplot.android.parsing.AtomTypes.YVariableAtom.java
com.wildplot.android.rendering.AdvancedPlotSheet.java
com.wildplot.android.rendering.BarGraph.java
com.wildplot.android.rendering.DrawableContainer.java
com.wildplot.android.rendering.FunctionDrawer.java
com.wildplot.android.rendering.FunctionDrawer_y.java
com.wildplot.android.rendering.Integral.java
com.wildplot.android.rendering.LegendDrawable.java
com.wildplot.android.rendering.LinesPoints.java
com.wildplot.android.rendering.Lines.java
com.wildplot.android.rendering.MultiScreenPart.java
com.wildplot.android.rendering.PieChart.java
com.wildplot.android.rendering.PlotSheet.java
com.wildplot.android.rendering.PointDrawer2D.java
com.wildplot.android.rendering.RelativeColorGradient.java
com.wildplot.android.rendering.ReliefDrawer.java
com.wildplot.android.rendering.XAxisBarGraph.java
com.wildplot.android.rendering.XAxisHistoGram.java
com.wildplot.android.rendering.XAxis.java
com.wildplot.android.rendering.XGrid.java
com.wildplot.android.rendering.YAxisBarGraph.java
com.wildplot.android.rendering.YAxisHistoGram.java
com.wildplot.android.rendering.YAxis.java
com.wildplot.android.rendering.YGrid.java
com.wildplot.android.rendering.graphics.wrapper.BasicStroke.java
com.wildplot.android.rendering.graphics.wrapper.BufferedImage.java
com.wildplot.android.rendering.graphics.wrapper.Color.java
com.wildplot.android.rendering.graphics.wrapper.FontMetrics.java
com.wildplot.android.rendering.graphics.wrapper.Graphics2D.java
com.wildplot.android.rendering.graphics.wrapper.Graphics.java
com.wildplot.android.rendering.graphics.wrapper.Rectangle.java
com.wildplot.android.rendering.graphics.wrapper.Stroke.java
com.wildplot.android.rendering.interfaces.Drawable.java
com.wildplot.android.rendering.interfaces.Function2D.java
com.wildplot.android.rendering.interfaces.Function3D.java
com.wildplot.android.rendering.interfaces.Legendable.java
com.wildplot.android.rendering.interfaces.StepFunction2D.java