In-progress content provider

pull/14/head
Tim Su 14 years ago
parent 44a3310548
commit 17a9ec4cb2

@ -16,7 +16,7 @@ public class AstridApiConstants {
// --- General Constants // --- General Constants
/** /**
* Astrid application package name * Astrid broadcast base package name
*/ */
public static final String PACKAGE = "com.todoroo.astrid"; public static final String PACKAGE = "com.todoroo.astrid";
@ -199,16 +199,4 @@ public class AstridApiConstants {
*/ */
public static final String BROADCAST_EVENT_TASK_REPEATED = PACKAGE + ".TASK_REPEATED"; public static final String BROADCAST_EVENT_TASK_REPEATED = PACKAGE + ".TASK_REPEATED";
// --- SQL Constants
/**
* Table name for tasks
*/
public static final String TASK_TABLE = "tasks";
/**
* Table name for metadata
*/
public static final String METADATA_TABLE = "metadata";
} }

@ -41,6 +41,11 @@ public class GenericDao<TYPE extends AbstractModel> {
setDatabase(database); setDatabase(database);
} }
/** Gets table associated with this DAO */
public Table getTable() {
return table;
}
/** /**
* Sets up a database * Sets up a database
* @param database * @param database

@ -130,6 +130,7 @@ public class ShortcutActivity extends Activity {
public static Intent createIntent(Filter filter) { public static Intent createIntent(Filter filter) {
Intent shortcutIntent = new Intent(ContextManager.getContext(), Intent shortcutIntent = new Intent(ContextManager.getContext(),
ShortcutActivity.class); ShortcutActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW); shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_TITLE, filter.title); shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_TITLE, filter.title);
shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_SQL, filter.sqlQuery); shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_SQL, filter.sqlQuery);

@ -20,6 +20,7 @@ import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.data.Table; import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
/** /**
* Data Model which represents a task users need to accomplish. * Data Model which represents a task users need to accomplish.
@ -30,10 +31,14 @@ import com.todoroo.andlib.utility.DateUtilities;
@SuppressWarnings("nls") @SuppressWarnings("nls")
public final class Task extends AbstractModel { public final class Task extends AbstractModel {
// --- table // --- table and uri
/** table for this model */
public static final Table TABLE = new Table("tasks", Task.class); public static final Table TABLE = new Table("tasks", Task.class);
/** content uri for this model */
public static final String CONTENT_URI = "content://" + AstridApiConstants.PACKAGE + "/tasks/";
// --- properties // --- properties
/** ID */ /** ID */

@ -3,10 +3,6 @@
*/ */
package com.todoroo.astrid.provider; package com.todoroo.astrid.provider;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Map.Entry;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.ContentValues; import android.content.ContentValues;
@ -15,20 +11,36 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import com.todoroo.andlib.data.AbstractDatabase; import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.GenericDao;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.AstridDependencyInjector;
/** /**
* Astrid Content Provider. Combines all Astrid tables into a single content * Astrid 3 Content Provider. There are two ways to use this content provider:
* provider that can be queried, inserted into, and deleted from. * <ul>
* <li>access it directly just like any other content provider
* <li>use the DAO classes from the Astrid API library
* </ul>
*
* The following base URI's are supported:
* <ul>
* <li>content://com.todoroo.astrid/tasks - task data ({@link Task})
* <li>content://com.todoroo.astrid/metadata - task metadata ({@link Metadata})
* <li>content://com.todoroo.astrid/store - non-task store data ({@link StoreObject})
* </ul>
*
* Each URI supports the following components:
* <ul>
* <li>/ - query for all items
* *
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
@ -40,40 +52,32 @@ public class Astrid3ContentProvider extends ContentProvider {
AstridDependencyInjector.initialize(); AstridDependencyInjector.initialize();
} }
/** URI for making a request over all tasks */ /** URI for making a request over all items */
private static final int URI_DIR = 1; private static final int URI_DIR = 1;
/** URI for making a request over a single task by id */ /** URI for making a request over a single item by id */
private static final int URI_ITEM = 2; private static final int URI_ITEM = 2;
/** URI for making a request over all tasks grouped by some field */ /** URI for making a request over all items grouped by some field */
private static final int URI_GROUP = 3; private static final int URI_GROUP = 3;
/** URI for making a request over all tasks grouped by some field */
private static final int URI_DIR_WITH_METADATA = 4;
protected static final String PROVIDER_NAME = AstridContentProvider.PROVIDER;
// --- instance variables // --- instance variables
private final UriMatcher uriMatcher; private final UriMatcher uriMatcher;
@Autowired @Autowired
private AbstractDatabase database; private Database database;
@Autowired @Autowired
private ExceptionService exceptionService; private TaskDao taskDao;
/** Container classes for avoiding multiple object creation */ @Autowired
private ContentValues taskValues, metadataValues, tempValues; private ExceptionService exceptionService;
/** List of task columns in a set form. Lazy initialized */
private WeakReference<HashSet<String>> taskColumnSetRef = null;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
try { try {
((Database)database).openForWriting(getContext()); database.openForWriting();
return database.getDatabase() != null; return database.getDatabase() != null;
} catch (Exception e) { } catch (Exception e) {
exceptionService.reportError("astrid-provider", e); exceptionService.reportError("astrid-provider", e);
@ -81,14 +85,13 @@ public class Astrid3ContentProvider extends ContentProvider {
} }
} }
public ContentProvider() { public Astrid3ContentProvider() {
DependencyInjectionService.getInstance().inject(this); DependencyInjectionService.getInstance().inject(this);
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "items", URI_DIR); uriMatcher.addURI(Task.CONTENT_URI, "", URI_DIR);
uriMatcher.addURI(PROVIDER_NAME, "#", URI_ITEM); uriMatcher.addURI(Task.CONTENT_URI, "#", URI_ITEM);
uriMatcher.addURI(PROVIDER_NAME, "groupby/*", URI_GROUP); uriMatcher.addURI(Task.CONTENT_URI, "groupby/*", URI_GROUP);
uriMatcher.addURI(PROVIDER_NAME, "itemsWith/*", URI_DIR_WITH_METADATA);
setReadPermission(AstridApiConstants.PERMISSION_READ); setReadPermission(AstridApiConstants.PERMISSION_READ);
setWritePermission(AstridApiConstants.PERMISSION_WRITE); setWritePermission(AstridApiConstants.PERMISSION_WRITE);
@ -99,35 +102,90 @@ public class Astrid3ContentProvider extends ContentProvider {
switch (uriMatcher.match(uri)) { switch (uriMatcher.match(uri)) {
case URI_DIR: case URI_DIR:
case URI_GROUP: case URI_GROUP:
case URI_DIR_WITH_METADATA: return "vnd.android.cursor.dir/vnd.astrid";
return "vnd.android.cursor.dir/vnd.todoroo";
case URI_ITEM: case URI_ITEM:
return "vnd.android.cursor/vnd.todoroo.item"; return "vnd.android.cursor/vnd.astrid.item";
default: default:
throw new IllegalArgumentException("Unsupported URI: " + uri); throw new IllegalArgumentException("Unsupported URI: " + uri);
} }
} }
protected Uri makeContentUri() { /* ======================================================================
return Uri.parse("content://" + PROVIDER_NAME); * ========================================================== helpers ===
* ====================================================================== */
private class UriHelper<TYPE extends AbstractModel> {
/** empty model. used for insert */
public TYPE model;
/** dao */
public GenericDao<TYPE> dao;
/** creates from given model */
public void create() {
dao.createNew(model);
}
/** updates from given model */
public void update() {
dao.saveExisting(model);
}
}
private UriHelper<?> generateHelper(Uri uri, boolean populateModel) {
if(uri.toString().startsWith(Task.CONTENT_URI)) {
UriHelper<Task> helper = new UriHelper<Task>();
helper.model = populateModel ? new Task() : null;
helper.dao = taskDao;
return helper;
}
throw new UnsupportedOperationException("Unknown URI " + uri);
} }
/* ====================================================================== /* ======================================================================
* =========================================================== delete === * =========================================================== delete ===
* ====================================================================== */ * ====================================================================== */
/**
* Delete from given table
* @return number of rows deleted
*/
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO switch (uriMatcher.match(uri)) {
// illegal operations
case URI_GROUP:
throw new IllegalArgumentException("Only the / or /# URI is valid"
+ " for deletion.");
// valid operations
case URI_ITEM: {
return 0;
}
case URI_DIR: {
return 0; return 0;
} }
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
/* ====================================================================== /* ======================================================================
* =========================================================== insert === * =========================================================== insert ===
* ====================================================================== */ * ====================================================================== */
/** /**
* Insert key/value pairs into metadata table with appropriate namespace. * Insert key/value pairs into given table
*/ */
@Override @Override
public Uri insert(Uri uri, ContentValues values) { public Uri insert(Uri uri, ContentValues values) {
@ -137,36 +195,18 @@ public class Astrid3ContentProvider extends ContentProvider {
case URI_ITEM: case URI_ITEM:
case URI_GROUP: case URI_GROUP:
case URI_DIR_WITH_METADATA: throw new IllegalArgumentException("Only the / URI is valid"
throw new IllegalArgumentException("Only the allItems URI is valid" + " for insertion.");
+ " for inserting a new task, I do not understand what you"
+ " are trying to do!");
// valid operations // valid operations
case URI_DIR: { case URI_DIR: {
// insert a task
taskValues = initialize(taskValues);
metadataValues = initialize(metadataValues);
separateTaskColumnsFromMetadata(values, taskValues, metadataValues);
// insert task columns, then insert metadata columns UriHelper<?> helper = generateHelper(uri, true);
long taskId = TodorooContentProvider.insertHelper(database, helper.model.mergeWith(values);
Database.TASK_TABLE, taskValues, Task.getStaticDefaultValues()); helper.create();
for(Entry<String,Object> metadata : metadataValues.valueSet()) { return ContentUris.withAppendedId(uri, helper.model.getId());
tempValues = initialize(tempValues);
tempValues.put(Metadata.KEY.name, metadata.getKey());
tempValues.put(Metadata.VALUE.name, metadata.getValue().toString());
TodorooContentProvider.insertHelper(database,
Database.METADATA_TABLE, tempValues, null);
}
if (taskId > 0) {
Uri _uri = ContentUris.withAppendedId(makeContentUri(), taskId);
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
} }
default: default:
@ -174,34 +214,12 @@ public class Astrid3ContentProvider extends ContentProvider {
} }
} }
/** Given a single ContentValues, separate into ones for tasks and ones
* for metadata */
private static void separateTaskColumnsFromMetadata(ContentValues values,
ContentValues taskCols, ContentValues metadataCols) {
// TODO
}
/** Initialize content provider */
private synchronized ContentValues initialize(ContentValues values) {
if(values == null)
return new ContentValues();
values.clear();
return values;
}
/** Assert that the provided values contain the key given */
private static void assertContains(ContentValues values, Property<?> key) {
if(!values.containsKey(key.name)) {
throw new IllegalArgumentException("ContentValues must contain key " + key);
}
}
/* ====================================================================== /* ======================================================================
* =========================================================== update === * =========================================================== update ===
* ====================================================================== */ * ====================================================================== */
/** /**
* Undscapes a string for use in a URI. Used internally to pass extra data * Unescapes a string for use in a URI. Used internally to pass extra data
* to the content provider. * to the content provider.
* @param component * @param component
* @return * @return
@ -296,8 +314,8 @@ public class Astrid3ContentProvider extends ContentProvider {
* ====================================================================== */ * ====================================================================== */
/** /**
* Query by metadata. * Query by task.
* * <p>
* Note that the "sortOrder" field actually can be used to append any * Note that the "sortOrder" field actually can be used to append any
* sort of clause to your SQL query as long as it is not also the * sort of clause to your SQL query as long as it is not also the
* name of a column * name of a column
@ -305,35 +323,21 @@ public class Astrid3ContentProvider extends ContentProvider {
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) { String[] selectionArgs, String sortOrder) {
HashSet<String> taskColumnSet = initializeTaskColumnSet();
SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); String groupBy = null;
StringBuilder tables = new StringBuilder(Database.TASK_TABLE).append(" t");
// walk through projection columns building tables and projections UriHelper<?> helper = generateHelper(uri, false);
for(int i = 0; i < projection.length; i++) { SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
if(taskColumnSet.contains(projection[i])) { builder.setTables(helper.dao.getTable().name);
String tableName = "m" + i;
projection[i] = String.format("%s.%s AS %s",
tableName, Metadata.VALUE, projection[i]);
tables.append(String.format(" LEFT OUTER JOIN %s %s ON t._id = %s.%s",
Database.METADATA_TABLE, tableName, tableName, Metadata.TASK.name));
builder.appendWhereEscapeString(String.format("%s.%s = '%s' AND ",
tableName, Metadata.KEY.name, projection[i]));
}
}
// add data from URI
String groupBy = null;
switch (uriMatcher.match(uri)) { switch (uriMatcher.match(uri)) {
case URI_GROUP: case URI_GROUP:
groupBy = uri.getPathSegments().get(1); groupBy = uri.getPathSegments().get(1);
case URI_DIR: case URI_DIR:
builder.appendWhere("TRUE");
break; break;
case URI_ITEM: case URI_ITEM:
String itemSelector = String.format("%s = '%s'", String itemSelector = String.format("%s = '%s'",
AstridTask.ID, uri.getPathSegments().get(1)); AbstractModel.ID_PROPERTY, uri.getPathSegments().get(1));
builder.appendWhere(itemSelector); builder.appendWhere(itemSelector);
break; break;
default: default:
@ -345,16 +349,4 @@ public class Astrid3ContentProvider extends ContentProvider {
return cursor; return cursor;
} }
/** helper method to lazy-initialize taskColumnSet */
private synchronized HashSet<String> initializeTaskColumnSet() {
if(taskColumnSetRef != null && taskColumnSetRef.get() != null)
return taskColumnSetRef.get();
HashSet<String> taskColumnSet = new HashSet<String>();
taskColumnSetRef = new WeakReference<HashSet<String>>(taskColumnSet);
for(Property<?> property : Task.PROPERTIES)
taskColumnSet.add(property.qualifiedName());
return taskColumnSet;
}
} }

Loading…
Cancel
Save