mirror of https://github.com/tasks/tasks
Added content provider, but it doesn't work yet!
parent
651c87408a
commit
44a3310548
@ -0,0 +1,360 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
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.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractDatabase;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.dao.Database;
|
||||
import com.todoroo.astrid.model.Metadata;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
|
||||
/**
|
||||
* Astrid Content Provider. Combines all Astrid tables into a single content
|
||||
* provider that can be queried, inserted into, and deleted from.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({"nls","unused"})
|
||||
public class Astrid3ContentProvider extends ContentProvider {
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
/** URI for making a request over all tasks */
|
||||
private static final int URI_DIR = 1;
|
||||
|
||||
/** URI for making a request over a single task by id */
|
||||
private static final int URI_ITEM = 2;
|
||||
|
||||
/** URI for making a request over all tasks grouped by some field */
|
||||
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
|
||||
|
||||
private final UriMatcher uriMatcher;
|
||||
|
||||
@Autowired
|
||||
private AbstractDatabase database;
|
||||
|
||||
@Autowired
|
||||
private ExceptionService exceptionService;
|
||||
|
||||
/** Container classes for avoiding multiple object creation */
|
||||
private ContentValues taskValues, metadataValues, tempValues;
|
||||
|
||||
/** List of task columns in a set form. Lazy initialized */
|
||||
private WeakReference<HashSet<String>> taskColumnSetRef = null;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
try {
|
||||
((Database)database).openForWriting(getContext());
|
||||
return database.getDatabase() != null;
|
||||
} catch (Exception e) {
|
||||
exceptionService.reportError("astrid-provider", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ContentProvider() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(PROVIDER_NAME, "items", URI_DIR);
|
||||
uriMatcher.addURI(PROVIDER_NAME, "#", URI_ITEM);
|
||||
uriMatcher.addURI(PROVIDER_NAME, "groupby/*", URI_GROUP);
|
||||
uriMatcher.addURI(PROVIDER_NAME, "itemsWith/*", URI_DIR_WITH_METADATA);
|
||||
|
||||
setReadPermission(AstridApiConstants.PERMISSION_READ);
|
||||
setWritePermission(AstridApiConstants.PERMISSION_WRITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
switch (uriMatcher.match(uri)) {
|
||||
case URI_DIR:
|
||||
case URI_GROUP:
|
||||
case URI_DIR_WITH_METADATA:
|
||||
return "vnd.android.cursor.dir/vnd.todoroo";
|
||||
case URI_ITEM:
|
||||
return "vnd.android.cursor/vnd.todoroo.item";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported URI: " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
protected Uri makeContentUri() {
|
||||
return Uri.parse("content://" + PROVIDER_NAME);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* =========================================================== delete ===
|
||||
* ====================================================================== */
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* =========================================================== insert ===
|
||||
* ====================================================================== */
|
||||
|
||||
/**
|
||||
* Insert key/value pairs into metadata table with appropriate namespace.
|
||||
*/
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
switch (uriMatcher.match(uri)) {
|
||||
|
||||
// illegal operations
|
||||
|
||||
case URI_ITEM:
|
||||
case URI_GROUP:
|
||||
case URI_DIR_WITH_METADATA:
|
||||
throw new IllegalArgumentException("Only the allItems URI is valid"
|
||||
+ " for inserting a new task, I do not understand what you"
|
||||
+ " are trying to do!");
|
||||
|
||||
// valid operations
|
||||
|
||||
case URI_DIR: {
|
||||
// insert a task
|
||||
taskValues = initialize(taskValues);
|
||||
metadataValues = initialize(metadataValues);
|
||||
separateTaskColumnsFromMetadata(values, taskValues, metadataValues);
|
||||
|
||||
// insert task columns, then insert metadata columns
|
||||
long taskId = TodorooContentProvider.insertHelper(database,
|
||||
Database.TASK_TABLE, taskValues, Task.getStaticDefaultValues());
|
||||
|
||||
for(Entry<String,Object> metadata : metadataValues.valueSet()) {
|
||||
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:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 ===
|
||||
* ====================================================================== */
|
||||
|
||||
/**
|
||||
* Undscapes a string for use in a URI. Used internally to pass extra data
|
||||
* to the content provider.
|
||||
* @param component
|
||||
* @return
|
||||
*/
|
||||
private static String unescapeUriComponent(String component) {
|
||||
return component.replace("%s", "/").replace("%o", "%");
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string for use as part of a URI string. Used internally to pass extra data
|
||||
* to the content provider.
|
||||
* @param component
|
||||
* @return
|
||||
*/
|
||||
private static String[] unpackUriSubComponent(String component) {
|
||||
return component.replace("$s", "|").replace("o", "$").split("|");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection,
|
||||
String[] selectionArgs) {
|
||||
/*switch (uriMatcher.match(uri)) {
|
||||
case URI_DIR_WITH_METADATA: {
|
||||
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
|
||||
String[] metadata = unpackUriSubComponent(uri.getPathSegments().get(1));
|
||||
StringBuilder tables = new StringBuilder(Database.TASK_TABLE).append(" t");
|
||||
for(int i = 0; i < metadata.length; i++) {
|
||||
String tableName = "m" + 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]));
|
||||
}
|
||||
}
|
||||
|
||||
case URI_DIR: {
|
||||
// UPDATE tasks SET ... WHERE ...
|
||||
taskValues = initialize(taskValues);
|
||||
metadataValues = initialize(metadataValues);
|
||||
separateTaskColumnsFromMetadata(values, taskValues, metadataValues);
|
||||
|
||||
count = database.getDatabase().update(getTableName(), valuesWithDefaults,
|
||||
selection, selectionArgs);
|
||||
|
||||
// SELECT _id WHERE ...
|
||||
|
||||
// INSERT OR REPLACE INTO metadata (...) VALUES (...)
|
||||
long taskId = TodorooContentProvider.insertHelper(database,
|
||||
Database.TASK_TABLE, taskValues, Task.getStaticDefaultValues());
|
||||
|
||||
for(Entry<String,Object> metadata : metadataValues.valueSet()) {
|
||||
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;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ITEM:
|
||||
String id = uri.getPathSegments().get(0);
|
||||
ContentValues newValues = new ContentValues();
|
||||
rewriteKeysFor(values, newValues);
|
||||
count = database.getDatabase().update(
|
||||
getTableName(),
|
||||
newValues,
|
||||
(AbstractModel.ID_PROPERTY + "=" + id)
|
||||
+ (!TextUtils.isEmpty(selection) ? " AND ("
|
||||
+ selection + ')' : ""), selectionArgs);
|
||||
break;
|
||||
case GROUP:
|
||||
throw new IllegalArgumentException("Invalid URI for update: " + uri);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return count;*/
|
||||
|
||||
return 0; // FIXME
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* ============================================================ query ===
|
||||
* ====================================================================== */
|
||||
|
||||
/**
|
||||
* Query by metadata.
|
||||
*
|
||||
* 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
|
||||
* name of a column
|
||||
*/
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
HashSet<String> taskColumnSet = initializeTaskColumnSet();
|
||||
|
||||
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
|
||||
StringBuilder tables = new StringBuilder(Database.TASK_TABLE).append(" t");
|
||||
|
||||
// walk through projection columns building tables and projections
|
||||
for(int i = 0; i < projection.length; i++) {
|
||||
if(taskColumnSet.contains(projection[i])) {
|
||||
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)) {
|
||||
case URI_GROUP:
|
||||
groupBy = uri.getPathSegments().get(1);
|
||||
case URI_DIR:
|
||||
builder.appendWhere("TRUE");
|
||||
break;
|
||||
case URI_ITEM:
|
||||
String itemSelector = String.format("%s = '%s'",
|
||||
AstridTask.ID, uri.getPathSegments().get(1));
|
||||
builder.appendWhere(itemSelector);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
|
||||
Cursor cursor = builder.query(database.getDatabase(), projection, selection, selectionArgs, groupBy, null, sortOrder);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
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…
Reference in New Issue