/*******************************************************************************
* BBC News Reader
* Released under the BSD License. See README or LICENSE.
* Copyright (c) 2011, Digital Lizard (Oscar Key, Thomas Boby)
* All rights reserved.
******************************************************************************/
package com.digitallizard.bbcnewsreader.data;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
public class DatabaseProvider extends ContentProvider {
/** constants **/
public static final String AUTHORITY = "com.digitallizard.bbcnewsreader";
public static final Uri CONTENT_URI = Uri.parse("content://com.digitallizard.bbcnewsreader");
public static final Uri CONTENT_URI_CATEGORIES = Uri.parse("content://com.digitallizard.bbcnewsreader/categories");
public static final Uri CONTENT_URI_ENABLED_CATEGORIES = Uri.withAppendedPath(CONTENT_URI_CATEGORIES, "enabled");
public static final Uri CONTENT_URI_DISABLED_CATEGORIES = Uri.withAppendedPath(CONTENT_URI_CATEGORIES, "disabled");
public static final Uri CONTENT_URI_CATEGORY_BY_ID = Uri.withAppendedPath(CONTENT_URI_CATEGORIES, "id");
public static final Uri CONTENT_URI_CATEGORY_BY_NAME = Uri.withAppendedPath(CONTENT_URI_CATEGORIES, "name");
public static final Uri CONTENT_URI_ITEMS = Uri.parse("content://com.digitallizard.bbcnewsreader/items");
public static final Uri CONTENT_URI_ITEMS_BY_CATEGORY = Uri.withAppendedPath(CONTENT_URI_ITEMS, "category");
public static final Uri CONTENT_URI_UNDOWNLOADED_ITEMS = Uri.withAppendedPath(CONTENT_URI_ITEMS, "undownloaded");
public static final Uri CONTENT_URI_RELATIONSHIPS = Uri.parse("content://com.digitallizard.bbcnewsreader/relationships");
// uri matcher helpers
private static final int CATEGORIES = 1;
private static final int CATEGORY_BY_ID = 8;
private static final int CATEGORY_BY_NAME = 7;
private static final int ENABLED_CATEGORIES = 2;
private static final int DISABLED_CATEGORIES = 10;
private static final int ITEMS = 4;
private static final int ITEM_BY_ID = 5;
private static final int ITEMS_BY_CATEGORY = 3;
private static final int UNDOWNLOADED_ITEMS = 6;
private static final int RELATIONSHIPS = 9;
// uri matcher
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, "categories", CATEGORIES);
uriMatcher.addURI(AUTHORITY, "categories/enabled", ENABLED_CATEGORIES);
uriMatcher.addURI(AUTHORITY, "categories/disabled", DISABLED_CATEGORIES);
uriMatcher.addURI(AUTHORITY, "categories/id/#", CATEGORY_BY_ID);
uriMatcher.addURI(AUTHORITY, "categories/name/*", CATEGORY_BY_NAME);
uriMatcher.addURI(AUTHORITY, "items/", ITEMS);
uriMatcher.addURI(AUTHORITY, "items/#", ITEM_BY_ID);
uriMatcher.addURI(AUTHORITY, "items/category/*", ITEMS_BY_CATEGORY);
uriMatcher.addURI(AUTHORITY, "items/undownloaded/*", UNDOWNLOADED_ITEMS);
uriMatcher.addURI(AUTHORITY, "relationships", RELATIONSHIPS);
}
/** variables **/
DatabaseHelper database;
boolean methodInsertWithConflictExists;
// compatibility checker
private void checkCompatibility() {
// check if the insertWithOnConflict exists
try {
SQLiteDatabase.class.getMethod("insertWithOnConflict", new Class[] { String.class, String.class, ContentValues.class, Integer.TYPE });
// success, this method exists, set the boolean
methodInsertWithConflictExists = true;
} catch (NoSuchMethodException e) {
// failure, set the boolean
methodInsertWithConflictExists = false;
}
}
// get functions
private Cursor getCategories(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return database.query(DatabaseHelper.CATEGORY_TABLE, projection, selection, selectionArgs, sortOrder);
}
private Cursor getEnabledCategories(String[] projection, String sortOrder) {
// define a selection to only retrieve enabled categories
String selection = DatabaseHelper.COLUMN_CATEGORY_ENABLED + "='1'";
// ask for categories by this selection
return getCategories(projection, selection, null, sortOrder);
}
private Cursor getDisabledCategories(String[] projection, String sortOrder) {
// define a selection to only retrieve disabled categories
String selection = DatabaseHelper.COLUMN_CATEGORY_ENABLED + "='0'";
// ask for categories by this selection
return getCategories(projection, selection, null, sortOrder);
}
private Cursor getItems(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// get items
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setDistinct(true);
queryBuilder.setTables(DatabaseHelper.ITEM_TABLE + " JOIN " + DatabaseHelper.RELATIONSHIP_TABLE + " ON " + DatabaseHelper.ITEM_TABLE + "."
+ DatabaseHelper.COLUMN_ITEM_ID + "=" + DatabaseHelper.RELATIONSHIP_TABLE + "." + DatabaseHelper.COLUMN_RELATIONSHIP_ITEM_ID);
return queryBuilder.query(database.getDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
}
private Cursor getItem(String[] projection, int id) {
String selection = DatabaseHelper.COLUMN_ITEM_ID + "=?";
String[] selectionArgs = new String[] { Integer.toString(id) };
return database.query(DatabaseHelper.ITEM_TABLE, projection, selection, selectionArgs, null);
}
private Cursor getItems(String[] projection, String category, String sortOrder) {
// get items in this category
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setDistinct(true);
queryBuilder.setTables(DatabaseHelper.ITEM_TABLE + " JOIN " + DatabaseHelper.RELATIONSHIP_TABLE + " ON " + DatabaseHelper.ITEM_TABLE + "."
+ DatabaseHelper.COLUMN_ITEM_ID + "=" + DatabaseHelper.RELATIONSHIP_TABLE + "." + DatabaseHelper.COLUMN_RELATIONSHIP_ITEM_ID);
String selection = DatabaseHelper.RELATIONSHIP_TABLE + "." + DatabaseHelper.COLUMN_RELATIONSHIP_CATEGORY_NAME + "=?";
String[] selectionArgs = new String[] { category };
return queryBuilder.query(database.getDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
}
private Cursor getUndownloadedItems(String[] projection, int numItems) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setDistinct(true);
queryBuilder.setTables(DatabaseHelper.ITEM_TABLE + " JOIN " + DatabaseHelper.RELATIONSHIP_TABLE + " ON " + DatabaseHelper.ITEM_TABLE + "."
+ DatabaseHelper.COLUMN_ITEM_ID + "=" + DatabaseHelper.RELATIONSHIP_TABLE + "." + DatabaseHelper.COLUMN_RELATIONSHIP_ITEM_ID);
queryBuilder.setTables("items JOIN categories_items ON items.item_Id=categories_items.itemId");
String selection = DatabaseHelper.RELATIONSHIP_TABLE + "." + DatabaseHelper.COLUMN_RELATIONSHIP_PRIORITY + "<?" + " AND ("
+ DatabaseHelper.COLUMN_ITEM_HTML + " IS NULL OR " + DatabaseHelper.COLUMN_ITEM_THUMBNAIL + " IS NULL)";
String[] selectionArgs = new String[] { Integer.toString(numItems) };
return queryBuilder.query(database.getDatabase(), projection, selection, selectionArgs, null, null, null);
}
private Uri insertItem(ContentValues values, String category) {
long id = -1; // will hold the id of the item, -1 for now to be safe
// retrieve useful stuff from the content values
int priority = values.getAsInteger(DatabaseHelper.COLUMN_RELATIONSHIP_PRIORITY);
values.remove(DatabaseHelper.COLUMN_RELATIONSHIP_PRIORITY);
String title = values.getAsString(DatabaseHelper.COLUMN_ITEM_TITLE);
// lock the database
database.beginTransaction();
try {
// query to see if this item is already in the database
String[] projection = new String[] { DatabaseHelper.COLUMN_ITEM_ID, DatabaseHelper.COLUMN_ITEM_TITLE };
String selection = DatabaseHelper.COLUMN_ITEM_URL + "=?";
String[] selectionArgs = new String[] { values.getAsString(DatabaseHelper.COLUMN_ITEM_URL) };
Cursor cursor = database.query(DatabaseHelper.ITEM_TABLE, projection, selection, selectionArgs, null);
// check if any rows were returned, null means no rows
if (cursor == null) {
// insert the items
id = database.insert(DatabaseHelper.ITEM_TABLE, values); // perform the insert operation
}
else if (cursor.getCount() == 1) {
// this item exists
cursor.moveToFirst();
id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ITEM_ID)); // save the id
// test to see if the title has changed
if (!cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_ITEM_TITLE)).equals(title)) {
values.clear();
// update the row
values.put(DatabaseHelper.COLUMN_ITEM_TITLE, title);
values.putNull(DatabaseHelper.COLUMN_ITEM_HTML);
values.putNull(DatabaseHelper.COLUMN_ITEM_THUMBNAIL);
selection = DatabaseHelper.COLUMN_ITEM_ID + "=?";
database.update(DatabaseHelper.ITEM_TABLE, values, selection, new String[] { Long.toString(id) });
}
cursor.close();
}
// associate the item with its category
values.clear();
values.put(DatabaseHelper.COLUMN_RELATIONSHIP_CATEGORY_NAME, category);
values.put(DatabaseHelper.COLUMN_RELATIONSHIP_ITEM_ID, id);
values.put(DatabaseHelper.COLUMN_RELATIONSHIP_PRIORITY, priority);
// insert it, if the required method doesn't exist, use the old one
if (methodInsertWithConflictExists) {
// FIXME performance: shouldn't replace every time
WrapBackwards.insertWithOnConflict(database, DatabaseHelper.RELATIONSHIP_TABLE, values, SQLiteDatabase.CONFLICT_REPLACE);
}
else {
// use an alternative method
try {
database.insertOrThrow(DatabaseHelper.RELATIONSHIP_TABLE, values);
} catch (SQLiteConstraintException e) {
// this item obviously already exists, replace it instead
database.replace(DatabaseHelper.RELATIONSHIP_TABLE, values);
} catch (SQLException e) {
// TODO handle this type of exception
}
}
// mark the transaction as successful
database.setTransactionSuccessful();
// return a uri to the new item
return Uri.withAppendedPath(DatabaseProvider.CONTENT_URI_ITEMS, Long.toString(id));
} finally {
// end the transaction, unlocking that database
database.endTransaction();
}
}
private int updateItem(ContentValues values, int id) {
String selection = DatabaseHelper.COLUMN_ITEM_ID + "=?";
String[] selectionArgs = new String[] { Integer.toString(id) };
return database.update(DatabaseHelper.ITEM_TABLE, values, selection, selectionArgs);
}
private int updateCategories(ContentValues values, String selection, String[] selectionArgs) {
return database.update(DatabaseHelper.CATEGORY_TABLE, values, selection, selectionArgs);
}
private int updateCategory(ContentValues values, int id) {
String selection = DatabaseHelper.COLUMN_CATEGORY_ID + "=?";
String[] selectionArgs = new String[] { Integer.toString(id) };
return database.update(DatabaseHelper.CATEGORY_TABLE, values, selection, selectionArgs);
}
private int updateCategory(ContentValues values, String name) {
String selection = DatabaseHelper.COLUMN_CATEGORY_NAME + "=?";
String[] selectionArgs = new String[] { name };
return database.update(DatabaseHelper.CATEGORY_TABLE, values, selection, selectionArgs);
}
private int updateRelationships(ContentValues values, String selection, String[] selectionArgs) {
return database.update(DatabaseHelper.RELATIONSHIP_TABLE, values, selection, selectionArgs);
}
private int deleteItem(int id) {
// delete this item from the item table
String selection = DatabaseHelper.COLUMN_ITEM_ID + "=?";
String[] selectionArgs = new String[] { Integer.toString(id) };
database.delete(DatabaseHelper.ITEM_TABLE, selection, selectionArgs);
// delete this item from the relationship table
selection = DatabaseHelper.COLUMN_RELATIONSHIP_ITEM_ID + "=?";
selectionArgs = new String[] { Integer.toString(id) };
database.delete(DatabaseHelper.RELATIONSHIP_TABLE, selection, selectionArgs);
return 1;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// try and match the uri
switch (uriMatcher.match(uri)) {
case ITEM_BY_ID:
// delete this item
int id = Integer.parseInt(uri.getLastPathSegment());
return deleteItem(id);
default:
throw new IllegalArgumentException("Unknown uri: " + uri.toString());
}
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// try and match the uri
switch (uriMatcher.match(uri)) {
case ITEMS_BY_CATEGORY:
// insert the provided item
String category = uri.getLastPathSegment();
return insertItem(values, category);
case CATEGORIES:
// insert the provided item
long id = database.insert(DatabaseHelper.CATEGORY_TABLE, values);
return Uri.withAppendedPath(DatabaseProvider.CONTENT_URI_CATEGORIES, Long.toString(id));
default:
throw new IllegalArgumentException("Unknown uri: " + uri.toString());
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// try and match the queried uri
switch (uriMatcher.match(uri)) {
case CATEGORIES:
// query the database for all the categories
return getCategories(projection, selection, selectionArgs, sortOrder);
case ENABLED_CATEGORIES:
// query the database for enabled categories
return getEnabledCategories(projection, sortOrder);
case DISABLED_CATEGORIES:
// query the database for disabled categories
return getDisabledCategories(projection, sortOrder);
case ITEMS:
// query the database for items
return this.getItems(projection, selection, selectionArgs, sortOrder);
case ITEM_BY_ID:
// query the database for this specific item
int id = Integer.parseInt(uri.getLastPathSegment());
return getItem(projection, id);
case ITEMS_BY_CATEGORY:
// query the database for items in this category
String category = uri.getLastPathSegment();
return getItems(projection, category, sortOrder);
case UNDOWNLOADED_ITEMS:
// query the database for undownloaded items of this type
int numItems = Integer.parseInt(uri.getLastPathSegment());
return getUndownloadedItems(projection, numItems);
default:
throw new IllegalArgumentException("Unknown uri: " + uri.toString());
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
switch (uriMatcher.match(uri)) {
case ITEM_BY_ID:
int id = Integer.parseInt(uri.getLastPathSegment());
return updateItem(values, id);
case CATEGORIES:
return updateCategories(values, selection, selectionArgs);
case CATEGORY_BY_ID:
id = Integer.parseInt(uri.getLastPathSegment());
return updateCategory(values, id);
case CATEGORY_BY_NAME:
String name = uri.getLastPathSegment();
return updateCategory(values, name);
case RELATIONSHIPS:
return updateRelationships(values, selection, selectionArgs);
default:
throw new IllegalArgumentException("Unknown uri: " + uri.toString());
}
}
@Override
public boolean onCreate() {
// initialise the database
database = new DatabaseHelper(this.getContext());
// check compatibility
checkCompatibility();
return true;
}
public DatabaseProvider() {
// TODO Auto-generated constructor stub
}
}