From 58e48399d54616937d90ea0fad91553e2f20c85e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 22 Apr 2013 16:14:28 -0700 Subject: [PATCH] Work in progress: Category class and content provider for recently-used categories list --- .../wikimedia/commons/category/Category.java | 151 ++++++++++++++++ .../category/CategoryContentProvider.java | 170 ++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 commons/src/main/java/org/wikimedia/commons/category/Category.java create mode 100644 commons/src/main/java/org/wikimedia/commons/category/CategoryContentProvider.java diff --git a/commons/src/main/java/org/wikimedia/commons/category/Category.java b/commons/src/main/java/org/wikimedia/commons/category/Category.java new file mode 100644 index 000000000..1e787ef6c --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/category/Category.java @@ -0,0 +1,151 @@ +package org.wikimedia.commons.category; + +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.text.TextUtils; +import org.wikimedia.commons.contributions.ContributionsContentProvider; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * User: brion + * Date: 4/22/13 + * Time: 3:37 PM + * To change this template use File | Settings | File Templates. + */ +public class Category /*implements Parcelable */{ + private ContentProviderClient client; + private Uri contentUri; + + private String name; + private Date lastUsed; + private int timesUsed; + + /* + // Do we need parcelable stuff? + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(name); + parcel.writeSerializable(lastUsed); + parcel.writeInt(timesUsed); + } + + public Category(Parcel in) { + name = in.readString(); + lastUsed = (Date)in.readSerializable(); + timesUsed = in.readInt(); + } + + public int describeContents() { + return 0; + } + */ + + // Getters/setters + public String getName() { + return name; + } + + public void setName(String name_) { + name = name_; + } + + public Date getLastUsed() { + // warning: Date objects are mutable. + return (Date)lastUsed.clone(); + } + + public void setLastUsed(Date lastUsed_) { + // warning: Date objects are mutable. + lastUsed = (Date)lastUsed_.clone(); + } + + public int getTimesUsed() { + return timesUsed; + } + + public void setTimesUsed(int timesUsed_) { + timesUsed = timesUsed_; + } + + public void incTimesUsed() { + timesUsed++; + } + + // Database/content-provider stuff + public void setContentProviderClient(ContentProviderClient client) { + this.client = client; + } + + public void save() { + try { + if(contentUri == null) { + contentUri = client.insert(CategoryContentProvider.BASE_URI, this.toContentValues()); + } else { + client.update(contentUri, toContentValues(), null, null); + } + } catch(RemoteException e) { + throw new RuntimeException(e); + } + } + + public ContentValues toContentValues() { + ContentValues cv = new ContentValues(); + cv.put(Table.COLUMN_NAME, getName()); + cv.put(Table.COLUMN_LAST_USED, getLastUsed().getTime()); + cv.put(Table.COLUMN_TIMES_USED, getTimesUsed()); + return cv; + } + + public static Category fromCursor(Cursor cursor) { + // Hardcoding column positions! + Category c = new Category(); + c.name = cursor.getString(0); + c.lastUsed = new Date(cursor.getLong(1)); + c.timesUsed = cursor.getInt(2); + return c; + } + + public static class Table { + public static final String TABLE_NAME = "categories"; + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_LAST_USED = "last_used"; + public static final String COLUMN_TIMES_USED = "times_used"; + + // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. + public static final String[] ALL_FIELDS = { + COLUMN_ID, + COLUMN_NAME, + COLUMN_LAST_USED, + COLUMN_TIMES_USED + }; + + + private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" + + COLUMN_ID + " INTEGER PRIMARY KEY," + + COLUMN_NAME + " STRING," + + COLUMN_LAST_USED + " INTEGER," // Will this roll over in 2038? :) + + COLUMN_TIMES_USED + " INTEGER" + + ");"; + + + public static void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_STATEMENT); + } + + public static void onUpdate(SQLiteDatabase db, int from, int to) { + if(from == to) { + return; + } + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/category/CategoryContentProvider.java b/commons/src/main/java/org/wikimedia/commons/category/CategoryContentProvider.java new file mode 100644 index 000000000..0276b29ca --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/category/CategoryContentProvider.java @@ -0,0 +1,170 @@ +package org.wikimedia.commons.category; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.data.DBOpenHelper; + +/** + * Created with IntelliJ IDEA. + * User: brion + * Date: 4/22/13 + * Time: 4:09 PM + * To change this template use File | Settings | File Templates. + */ +public class CategoryContentProvider extends ContentProvider { + + // ???? + private static final int CATEGORIES = 1; + private static final int CATEGORIES_ID = 2; + + public static final String AUTHORITY = "org.wikimedia.commons.categories.contentprovider"; + private static final String BASE_PATH = "categories"; + + public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); + + private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + static { + uriMatcher.addURI(AUTHORITY, BASE_PATH, CATEGORIES); + uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID); + } + + + public static Uri uriForId(int id) { + return Uri.parse(BASE_URI.toString() + "/" + id); + } + + private DBOpenHelper dbOpenHelper; + @Override + public boolean onCreate() { + dbOpenHelper = ((CommonsApplication)this.getContext().getApplicationContext()).getDbOpenHelper(); + return false; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables(Category.Table.TABLE_NAME); + + int uriType = uriMatcher.match(uri); + + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + Cursor cursor; + + switch(uriType) { + case CATEGORIES: + cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + break; + case CATEGORIES_ID: + cursor = queryBuilder.query(db, + Category.Table.ALL_FIELDS, + "_id = ?", + new String[] { uri.getLastPathSegment() }, + null, + null, + sortOrder + ); + break; + default: + throw new IllegalArgumentException("Unknown URI" + uri); + } + + cursor.setNotificationUri(getContext().getContentResolver(), uri); + + return cursor; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + long id = 0; + switch (uriType) { + case CATEGORIES: + id = sqlDB.insert(Category.Table.TABLE_NAME, null, contentValues); + break; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return Uri.parse(BASE_URI + "/" + id); + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + return 0; + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + Log.d("Commons", "Hello, bulk insert!"); + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + sqlDB.beginTransaction(); + switch (uriType) { + case CATEGORIES: + for(ContentValues value: values) { + Log.d("Commons", "Inserting! " + value.toString()); + sqlDB.insert(Category.Table.TABLE_NAME, null, value); + } + break; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + sqlDB.setTransactionSuccessful(); + sqlDB.endTransaction(); + getContext().getContentResolver().notifyChange(uri, null); + return values.length; + } + + @Override + public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) { + /* + SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false") + Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues + should be fine. So only issues are those that pass in via concating. + + In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise. + */ + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + int rowsUpdated = 0; + switch (uriType) { + case CATEGORIES: + rowsUpdated = sqlDB.update(Category.Table.TABLE_NAME, + contentValues, + selection, + selectionArgs); + break; + case CATEGORIES_ID: + int id = Integer.valueOf(uri.getLastPathSegment()); + + if (TextUtils.isEmpty(selection)) { + rowsUpdated = sqlDB.update(Category.Table.TABLE_NAME, + contentValues, + Category.Table.COLUMN_ID + " = ?", + new String[] { String.valueOf(id) } ); + } else { + throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID"); + } + break; + default: + throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType); + } + getContext().getContentResolver().notifyChange(uri, null); + return rowsUpdated; + } +} +