mirror of https://github.com/tasks/tasks
Remove AbstractDatabase, let android lock sqlite
parent
398bea94a1
commit
bea0216e66
@ -1,323 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* AbstractDatabase is a database abstraction which wraps a SQLite database.
|
||||
* <p>
|
||||
* Users of this class are in charge of the database's lifecycle - ensuring that
|
||||
* the database is open when needed and closed when usage is finished. Within an
|
||||
* activity, this is typically accomplished through the onResume and onPause
|
||||
* methods, though if the database is not needed for the activity's entire
|
||||
* lifecycle, it can be closed earlier.
|
||||
* <p>
|
||||
* Direct querying is not recommended for type safety reasons. Instead, use one
|
||||
* of the service classes to issue the request and return a {@link TodorooCursor}.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
abstract public class AbstractDatabase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AbstractDatabase.class);
|
||||
|
||||
// --- abstract methods
|
||||
|
||||
/**
|
||||
* @return database name
|
||||
*/
|
||||
protected abstract String getName();
|
||||
|
||||
/**
|
||||
* @return all tables in this database
|
||||
*/
|
||||
protected abstract Table[] getTables();
|
||||
|
||||
/**
|
||||
* @return database version
|
||||
*/
|
||||
protected abstract int getVersion();
|
||||
|
||||
/**
|
||||
* Called after database and tables are created. Use this method to
|
||||
* create indices and perform other database maintenance
|
||||
*/
|
||||
protected abstract void onCreateTables();
|
||||
|
||||
/**
|
||||
* Upgrades an open database from one version to the next
|
||||
* @return true if upgrade was handled, false otherwise
|
||||
*/
|
||||
protected abstract boolean onUpgrade(int oldVersion, int newVersion);
|
||||
|
||||
// --- protected variables
|
||||
|
||||
/**
|
||||
* SQLiteOpenHelper that takes care of database operations
|
||||
*/
|
||||
private SQLiteOpenHelper helper = null;
|
||||
|
||||
/**
|
||||
* Internal pointer to open database. Hides the fact that there is a
|
||||
* database and a wrapper by making a single monolithic interface
|
||||
*/
|
||||
protected SQLiteDatabase database = null;
|
||||
|
||||
// --- listeners
|
||||
|
||||
/**
|
||||
* Interface for responding to database changes
|
||||
*/
|
||||
public interface DatabaseUpdateListener {
|
||||
/**
|
||||
* Called when an INSERT, UPDATE, or DELETE occurs
|
||||
*/
|
||||
public void onDatabaseUpdated();
|
||||
}
|
||||
|
||||
private final ArrayList<DatabaseUpdateListener> listeners = new ArrayList<>();
|
||||
|
||||
public void addListener(DatabaseUpdateListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
private void onDatabaseUpdated() {
|
||||
for(DatabaseUpdateListener listener : listeners) {
|
||||
listener.onDatabaseUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the table containing these models
|
||||
*/
|
||||
public final Table getTable(Class<? extends AbstractModel> modelType) {
|
||||
for(Table table : getTables()) {
|
||||
if(table.modelClass.equals(modelType)) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException("Unknown model class " + modelType); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private synchronized void initializeHelper() {
|
||||
if(helper == null) {
|
||||
if(ContextManager.getContext() == null) {
|
||||
throw new NullPointerException("Null context creating database helper");
|
||||
}
|
||||
helper = new DatabaseHelper(ContextManager.getContext(), getName(), getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database for writing. Must be closed afterwards. If user is
|
||||
* out of disk space, database may be opened for reading instead
|
||||
*/
|
||||
public synchronized final void openForWriting() {
|
||||
initializeHelper();
|
||||
|
||||
if(database != null && !database.isReadOnly() && database.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
database = helper.getWritableDatabase();
|
||||
} catch (NullPointerException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new IllegalStateException(e);
|
||||
} catch (final RuntimeException original) {
|
||||
log.error(original.getMessage(), original);
|
||||
try {
|
||||
// provide read-only database
|
||||
openForReading();
|
||||
} catch (Exception readException) {
|
||||
log.error(readException.getMessage(), readException);
|
||||
// throw original write exception
|
||||
throw original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database for reading. Must be closed afterwards
|
||||
*/
|
||||
public synchronized final void openForReading() {
|
||||
initializeHelper();
|
||||
if(database != null && database.isOpen()) {
|
||||
return;
|
||||
}
|
||||
database = helper.getReadableDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database if it has been opened previously
|
||||
*/
|
||||
public synchronized final void close() {
|
||||
if(database != null) {
|
||||
database.close();
|
||||
}
|
||||
database = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return sql database. opens database if not yet open
|
||||
*/
|
||||
public synchronized final SQLiteDatabase getDatabase() {
|
||||
if(database == null) {
|
||||
AndroidUtilities.sleepDeep(300L);
|
||||
openForWriting();
|
||||
}
|
||||
return database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return human-readable database name for debugging
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DB:" + getName();
|
||||
}
|
||||
|
||||
// --- database wrapper
|
||||
|
||||
public synchronized Cursor rawQuery(String sql) {
|
||||
return getDatabase().rawQuery(sql, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.database.sqlite.SQLiteDatabase#insert(String table, String nullColumnHack, ContentValues values)
|
||||
*/
|
||||
public synchronized long insert(String table, String nullColumnHack, ContentValues values) {
|
||||
long result;
|
||||
try {
|
||||
result = getDatabase().insertOrThrow(table, nullColumnHack, values);
|
||||
} catch (SQLiteConstraintException e) { // Throw these exceptions
|
||||
throw e;
|
||||
} catch (Exception e) { // Suppress others
|
||||
log.error(e.getMessage(), e);
|
||||
result = -1;
|
||||
}
|
||||
onDatabaseUpdated();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.database.sqlite.SQLiteDatabase#delete(String table, String whereClause, String[] whereArgs)
|
||||
*/
|
||||
public synchronized int delete(String table, String whereClause, String[] whereArgs) {
|
||||
int result = getDatabase().delete(table, whereClause, whereArgs);
|
||||
onDatabaseUpdated();
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized int update(String table, ContentValues values, String whereClause) {
|
||||
int result = getDatabase().update(table, values, whereClause, null);
|
||||
onDatabaseUpdated();
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- helper classes
|
||||
|
||||
/**
|
||||
* Default implementation of Astrid database helper
|
||||
*/
|
||||
private class DatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public DatabaseHelper(Context context, String name, int version) {
|
||||
super(context, name, null, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the database tables
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onCreate(SQLiteDatabase db) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
SqlConstructorVisitor sqlVisitor = new SqlConstructorVisitor();
|
||||
|
||||
// create tables
|
||||
for(Table table : getTables()) {
|
||||
sql.append("CREATE TABLE IF NOT EXISTS ").append(table.name).append('(').
|
||||
append(AbstractModel.ID_PROPERTY).append(" INTEGER PRIMARY KEY AUTOINCREMENT");
|
||||
for(Property<?> property : table.getProperties()) {
|
||||
if(AbstractModel.ID_PROPERTY.name.equals(property.name)) {
|
||||
continue;
|
||||
}
|
||||
sql.append(',').append(property.accept(sqlVisitor, null));
|
||||
}
|
||||
sql.append(')');
|
||||
db.execSQL(sql.toString());
|
||||
sql.setLength(0);
|
||||
}
|
||||
|
||||
// post-table-creation
|
||||
database = db;
|
||||
onCreateTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to upgrade the database to a new version
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
log.info("Upgrading database from version {} to {}.", oldVersion, newVersion);
|
||||
|
||||
database = db;
|
||||
try {
|
||||
if(!AbstractDatabase.this.onUpgrade(oldVersion, newVersion)) {
|
||||
// We don't know how to handle this case because someone forgot to
|
||||
// implement the upgrade. We can't drop tables, we can only
|
||||
// throw a nasty exception at this time
|
||||
|
||||
throw new IllegalStateException("Missing database migration " +
|
||||
"from " + oldVersion + " to " + newVersion);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("database-upgrade-{}-{}-{}", getName(), oldVersion, newVersion, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor that returns SQL constructor for this property
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class SqlConstructorVisitor implements PropertyVisitor<String, Void> {
|
||||
|
||||
@Override
|
||||
public String visitInteger(Property<Integer> property, Void data) {
|
||||
return String.format("%s INTEGER", property.getColumnName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitLong(Property<Long> property, Void data) {
|
||||
return String.format("%s INTEGER", property.getColumnName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitString(Property<String> property, Void data) {
|
||||
return String.format("%s TEXT", property.getColumnName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
/**
|
||||
* Visitor that returns SQL constructor for this property
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class SqlConstructorVisitor implements Property.PropertyVisitor<String, Void> {
|
||||
|
||||
@Override
|
||||
public String visitInteger(Property<Integer> property, Void data) {
|
||||
return String.format("%s INTEGER", property.getColumnName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitLong(Property<Long> property, Void data) {
|
||||
return String.format("%s INTEGER", property.getColumnName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitString(Property<String> property, Void data) {
|
||||
return String.format("%s TEXT", property.getColumnName());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.todoroo.astrid.dao;
|
||||
|
||||
public interface DatabaseUpdateListener {
|
||||
public void onDatabaseUpdated();
|
||||
}
|
Loading…
Reference in New Issue