From 780af9d07d9bb9988e2dea2908dfe9c8d4b0763e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Wed, 27 Mar 2013 18:08:46 +0530 Subject: [PATCH] Initial cut of Modifications syncing Provides one naive modifier (which blindly adds categories). Provides a sync service & a content provider. Insert appropriate items into the content provider and wait for the sync to happen. Sync currently likes to 'fail early' rather than recover. Blank post upload activity also present, simply adds random category to the page that was uploaded. Will need appropriate UI --- commons/AndroidManifest.xml | 24 ++- commons/res/layout/activity_post_upload.xml | 8 + commons/res/values/strings.xml | 1 + ...ter.xml => contributions_sync_adapter.xml} | 0 .../res/xml/modifications_sync_adapter.xml | 9 + .../org/wikimedia/commons/ShareActivity.java | 5 +- .../wikimedia/commons/auth/LoginActivity.java | 2 + .../commons/contributions/Contribution.java | 7 +- .../ContributionsContentProvider.java | 17 +- .../wikimedia/commons/data/DBOpenHelper.java | 5 +- .../modifications/CategoryModifier.java | 49 ++++++ .../ModificationsContentProvider.java | 160 ++++++++++++++++++ .../ModificationsSyncAdapter.java | 138 +++++++++++++++ .../ModificationsSyncService.java | 26 +++ .../modifications/ModifierSequence.java | 142 ++++++++++++++++ .../commons/modifications/PageModifier.java | 37 ++++ .../modifications/PostUploadActivity.java | 28 +++ 17 files changed, 650 insertions(+), 8 deletions(-) create mode 100644 commons/res/layout/activity_post_upload.xml rename commons/res/xml/{syncadapter.xml => contributions_sync_adapter.xml} (100%) create mode 100644 commons/res/xml/modifications_sync_adapter.xml create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/CategoryModifier.java create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/ModificationsContentProvider.java create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncAdapter.java create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncService.java create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/ModifierSequence.java create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/PageModifier.java create mode 100644 commons/src/main/java/org/wikimedia/commons/modifications/PostUploadActivity.java diff --git a/commons/AndroidManifest.xml b/commons/AndroidManifest.xml index c60f0119f..daa741489 100644 --- a/commons/AndroidManifest.xml +++ b/commons/AndroidManifest.xml @@ -33,6 +33,9 @@ android:name=".auth.LoginActivity" android:theme="@style/NoTitle" > + + android:resource="@xml/contributions_sync_adapter" /> + + + + + + + + + diff --git a/commons/res/layout/activity_post_upload.xml b/commons/res/layout/activity_post_upload.xml new file mode 100644 index 000000000..752ded6d2 --- /dev/null +++ b/commons/res/layout/activity_post_upload.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/commons/res/values/strings.xml b/commons/res/values/strings.xml index 2835964c5..fdeb21454 100644 --- a/commons/res/values/strings.xml +++ b/commons/res/values/strings.xml @@ -51,6 +51,7 @@ Upload Name this set + Modifications No uploads yet diff --git a/commons/res/xml/syncadapter.xml b/commons/res/xml/contributions_sync_adapter.xml similarity index 100% rename from commons/res/xml/syncadapter.xml rename to commons/res/xml/contributions_sync_adapter.xml diff --git a/commons/res/xml/modifications_sync_adapter.xml b/commons/res/xml/modifications_sync_adapter.xml new file mode 100644 index 000000000..e85d9a6d4 --- /dev/null +++ b/commons/res/xml/modifications_sync_adapter.xml @@ -0,0 +1,9 @@ + + + diff --git a/commons/src/main/java/org/wikimedia/commons/ShareActivity.java b/commons/src/main/java/org/wikimedia/commons/ShareActivity.java index 14c009026..c0b4e2d09 100644 --- a/commons/src/main/java/org/wikimedia/commons/ShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/ShareActivity.java @@ -14,6 +14,7 @@ import android.view.*; import org.wikimedia.commons.contributions.*; import org.wikimedia.commons.auth.*; +import org.wikimedia.commons.modifications.PostUploadActivity; public class ShareActivity extends AuthenticatedActivity { @@ -64,11 +65,13 @@ public class ShareActivity extends AuthenticatedActivity { @Override protected void onPostExecute(Contribution contribution) { super.onPostExecute(contribution); + Intent postUploadIntent = new Intent(ShareActivity.this, PostUploadActivity.class); + postUploadIntent.putExtra(PostUploadActivity.EXTRA_MEDIA_URI, contribution.getContentUri()); + startActivity(postUploadIntent); finish(); } } - @Override public void onBackPressed() { super.onBackPressed(); diff --git a/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java b/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java index 1455e0c08..7b1ac8553 100644 --- a/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java @@ -16,6 +16,7 @@ import android.support.v4.app.NavUtils; import org.wikimedia.commons.*; import org.wikimedia.commons.EventLog; import org.wikimedia.commons.contributions.*; +import org.wikimedia.commons.modifications.ModificationsContentProvider; public class LoginActivity extends AccountAuthenticatorActivity { @@ -64,6 +65,7 @@ public class LoginActivity extends AccountAuthenticatorActivity { } // FIXME: If the user turns it off, it shouldn't be auto turned back on ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default! + ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! context.finish(); } else { int response; diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java index 71a70f1d2..20240a6c0 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java @@ -297,8 +297,11 @@ public class Contribution extends Media { onUpdate(db, from, to); return; } - db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); - onCreate(db); + if(from == 3) { + // Do nothing + from++; + return; + } } } } diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsContentProvider.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsContentProvider.java index 438990fda..acb7c62a8 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsContentProvider.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsContentProvider.java @@ -45,16 +45,27 @@ public class ContributionsContentProvider extends ContentProvider{ int uriType = uriMatcher.match(uri); + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + Cursor cursor; + switch(uriType) { case CONTRIBUTIONS: + cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + break; + case CONTRIBUTIONS_ID: + cursor = queryBuilder.query(db, + Contribution.Table.ALL_FIELDS, + "_id = ?", + new String[] { uri.getLastPathSegment() }, + null, + null, + sortOrder + ); break; default: throw new IllegalArgumentException("Unknown URI" + uri); } - SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - - Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; diff --git a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java index e328a31d9..011baa429 100644 --- a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java +++ b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java @@ -4,11 +4,12 @@ import android.content.*; import android.database.sqlite.*; import org.wikimedia.commons.contributions.*; +import org.wikimedia.commons.modifications.ModifierSequence; public class DBOpenHelper extends SQLiteOpenHelper{ private static final String DATABASE_NAME = "commons.db"; - private static final int DATABASE_VERSION = 3; + private static final int DATABASE_VERSION = 4; public DBOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -17,10 +18,12 @@ public class DBOpenHelper extends SQLiteOpenHelper{ @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { Contribution.Table.onCreate(sqLiteDatabase); + ModifierSequence.Table.onCreate(sqLiteDatabase); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) { Contribution.Table.onUpdate(sqLiteDatabase, from, to); + ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to); } } diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/CategoryModifier.java b/commons/src/main/java/org/wikimedia/commons/modifications/CategoryModifier.java new file mode 100644 index 000000000..00838ade3 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/CategoryModifier.java @@ -0,0 +1,49 @@ +package org.wikimedia.commons.modifications; + +import android.os.Bundle; +import android.os.Parcel; +import android.text.TextUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.*; + +public class CategoryModifier extends PageModifier { + + + public static String PARAM_CATEGORIES = "categories"; + + public static String MODIFIER_NAME = "CategoriesModifier"; + + public CategoryModifier(String... categories) { + super(MODIFIER_NAME); + JSONArray categoriesArray = new JSONArray(); + for(String category: categories) { + categoriesArray.put(category); + } + try { + params.putOpt(PARAM_CATEGORIES, categoriesArray); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public CategoryModifier(JSONObject data) { + super(MODIFIER_NAME); + this.params = data; + } + + @Override + public String doModification(String pageName, String pageContents) { + JSONArray categories; + categories = params.optJSONArray(PARAM_CATEGORIES); + + StringBuffer categoriesString = new StringBuffer(); + for(int i=0; i < categories.length(); i++) { + String category = categories.optString(i); + categoriesString.append("\n[[Category:").append(category).append("]]"); + } + return pageContents + categoriesString.toString(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsContentProvider.java b/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsContentProvider.java new file mode 100644 index 000000000..dc7beff75 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsContentProvider.java @@ -0,0 +1,160 @@ +package org.wikimedia.commons.modifications; + +import android.content.*; +import android.database.*; +import android.database.sqlite.*; +import android.net.*; +import android.text.*; +import android.util.*; + +import org.wikimedia.commons.*; +import org.wikimedia.commons.data.*; + +public class ModificationsContentProvider extends ContentProvider{ + + private static final int MODIFICATIONS = 1; + private static final int MODIFICATIONS_ID = 2; + + public static final String AUTHORITY = "org.wikimedia.commons.modifications.contentprovider"; + private static final String BASE_PATH = "modifications"; + + 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, MODIFICATIONS); + uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_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(ModifierSequence.Table.TABLE_NAME); + + int uriType = uriMatcher.match(uri); + + switch(uriType) { + case MODIFICATIONS: + break; + default: + throw new IllegalArgumentException("Unknown URI" + uri); + } + + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + + Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + 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 MODIFICATIONS: + id = sqlDB.insert(ModifierSequence.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) { + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + switch (uriType) { + case MODIFICATIONS_ID: + String id = uri.getLastPathSegment(); + sqlDB.delete(ModifierSequence.Table.TABLE_NAME, + "_id = ?", + new String[] { id } + ); + return 1; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + } + + @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 MODIFICATIONS: + for(ContentValues value: values) { + Log.d("Commons", "Inserting! " + value.toString()); + sqlDB.insert(ModifierSequence.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 MODIFICATIONS: + rowsUpdated = sqlDB.update(ModifierSequence.Table.TABLE_NAME, + contentValues, + selection, + selectionArgs); + break; + case MODIFICATIONS_ID: + int id = Integer.valueOf(uri.getLastPathSegment()); + + if (TextUtils.isEmpty(selection)) { + rowsUpdated = sqlDB.update(ModifierSequence.Table.TABLE_NAME, + contentValues, + ModifierSequence.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; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncAdapter.java b/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncAdapter.java new file mode 100644 index 000000000..12b130398 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncAdapter.java @@ -0,0 +1,138 @@ +package org.wikimedia.commons.modifications; + +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.*; +import android.database.Cursor; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import android.accounts.Account; +import android.os.Bundle; + +import java.io.*; +import java.util.*; + +import org.mediawiki.api.*; +import org.wikimedia.commons.Utils; +import org.wikimedia.commons.*; +import org.wikimedia.commons.contributions.Contribution; +import org.wikimedia.commons.contributions.ContributionsContentProvider; + + +public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { + + public ModificationsSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + @Override + public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) { + // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you! + + Cursor allModifications; + try { + allModifications = contentProviderClient.query(ModificationsContentProvider.BASE_URI, null, null, null, null); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + // Exit early if nothing to do + if(allModifications == null || allModifications.getCount() == 0) { + Log.d("Commons", "No modifications to perform"); + return; + } + + CommonsApplication app = (CommonsApplication)getContext().getApplicationContext(); + String authCookie; + try { + authCookie = AccountManager.get(app).blockingGetAuthToken(account, "", false); + } catch (OperationCanceledException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (AuthenticatorException e) { + throw new RuntimeException(e); + } + + MWApi api = app.getApi(); + api.setAuthCookie(authCookie); + String editToken; + + ApiResult requestResult, responseResult; + try { + editToken = api.getEditToken(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + + allModifications.moveToFirst(); + + Log.d("Commons", "Found " + allModifications.getCount() + " modifications to execute"); + + ContentProviderClient contributionsClient = null; + try { + contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); + + while(!allModifications.isAfterLast()) { + ModifierSequence sequence = ModifierSequence.fromCursor(allModifications); + sequence.setContentProviderClient(contentProviderClient); + Contribution contrib; + + Cursor contributionCursor; + try { + contributionCursor = contributionsClient.query(sequence.getMediaUri(), null, null, null, null); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + contributionCursor.moveToFirst(); + contrib = Contribution.fromCursor(contributionCursor); + + if(contrib.getState() == Contribution.STATE_COMPLETED) { + + try { + requestResult = api.action("query") + .param("prop", "revisions") + .param("rvprop", "timestamp|content") + .param("titles", contrib.getFilename()) + .get(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Log.d("Commons", "Page content is " + Utils.getStringFromDOM(requestResult.getDocument())); + String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev"); + String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent); + + try { + responseResult = api.action("edit") + .param("title", contrib.getFilename()) + .param("token", editToken) + .param("text", processedPageContent) + .post(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Log.d("Commons", "Response is" + Utils.getStringFromDOM(responseResult.getDocument())); + + String result = responseResult.getString("/api/edit/@result"); + if(!result.equals("Success")) { + throw new RuntimeException(); + } + + sequence.delete(); + allModifications.moveToNext(); + } + + } + } finally { + if(contributionsClient != null) { + contributionsClient.release(); + } + + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncService.java b/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncService.java new file mode 100644 index 000000000..86a80fa94 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/ModificationsSyncService.java @@ -0,0 +1,26 @@ +package org.wikimedia.commons.modifications; + +import android.app.*; +import android.content.*; +import android.os.*; + +public class ModificationsSyncService extends Service { + + private static final Object sSyncAdapterLock = new Object(); + + private static ModificationsSyncAdapter sSyncAdapter = null; + + @Override + public void onCreate() { + synchronized (sSyncAdapterLock) { + if (sSyncAdapter == null) { + sSyncAdapter = new ModificationsSyncAdapter(getApplicationContext(), true); + } + } + } + + @Override + public IBinder onBind(Intent intent) { + return sSyncAdapter.getSyncAdapterBinder(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/ModifierSequence.java b/commons/src/main/java/org/wikimedia/commons/modifications/ModifierSequence.java new file mode 100644 index 000000000..c58330185 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/ModifierSequence.java @@ -0,0 +1,142 @@ +package org.wikimedia.commons.modifications; + +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.RemoteException; +import android.text.TextUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.wikimedia.commons.contributions.ContributionsContentProvider; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; + +public class ModifierSequence { + private Uri mediaUri; + private ArrayList modifiers; + private Uri contentUri; + private ContentProviderClient client; + + public ModifierSequence(Uri mediaUri) { + this.mediaUri = mediaUri; + modifiers = new ArrayList(); + } + + public ModifierSequence(Uri mediaUri, JSONObject data) { + this(mediaUri); + JSONArray modifiersJSON = data.optJSONArray("modifiers"); + for(int i=0; i< modifiersJSON.length(); i++) { + modifiers.add(PageModifier.fromJSON(modifiersJSON.optJSONObject(i))); + } + } + + public Uri getMediaUri() { + return mediaUri; + } + + public void queueModifier(PageModifier modifier) { + modifiers.add(modifier); + } + + public String executeModifications(String pageName, String pageContents) { + for(PageModifier modifier: modifiers) { + pageContents = modifier.doModification(pageName, pageContents); + } + return pageContents; + } + + public JSONObject toJSON() { + JSONObject data = new JSONObject(); + try { + JSONArray modifiersJSON = new JSONArray(); + for(PageModifier modifier: modifiers) { + modifiersJSON.put(modifier.toJSON()); + } + data.put("modifiers", modifiersJSON); + return data; + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public ContentValues toContentValues() { + ContentValues cv = new ContentValues(); + cv.put(Table.COLUMN_MEDIA_URI, mediaUri.toString()); + cv.put(Table.COLUMN_DATA, toJSON().toString()); + return cv; + } + + public static ModifierSequence fromCursor(Cursor cursor) { + // Hardcoding column positions! + ModifierSequence ms = null; + try { + ms = new ModifierSequence(Uri.parse(cursor.getString(1)), new JSONObject(cursor.getString(2))); + } catch (JSONException e) { + throw new RuntimeException(e); + } + ms.contentUri = ModificationsContentProvider.uriForId(cursor.getInt(0)); + + return ms; + } + + public void save() { + try { + if(contentUri == null) { + contentUri = client.insert(ModificationsContentProvider.BASE_URI, this.toContentValues()); + } else { + client.update(contentUri, toContentValues(), null, null); + } + } catch(RemoteException e) { + throw new RuntimeException(e); + } + } + + public void delete() { + try { + client.delete(contentUri, null, null); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void setContentProviderClient(ContentProviderClient client) { + this.client = client; + } + + public static class Table { + public static final String TABLE_NAME = "modifications"; + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_MEDIA_URI = "mediauri"; + public static final String COLUMN_DATA = "data"; + + // 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_MEDIA_URI, + COLUMN_DATA + }; + + + private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" + + "_id INTEGER PRIMARY KEY," + + "mediauri STRING," + + "data STRING" + + ");"; + + + public static void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_STATEMENT); + } + + public static void onUpdate(SQLiteDatabase db, int from, int to) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); + onCreate(db); + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/PageModifier.java b/commons/src/main/java/org/wikimedia/commons/modifications/PageModifier.java new file mode 100644 index 000000000..fe09307a9 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/PageModifier.java @@ -0,0 +1,37 @@ +package org.wikimedia.commons.modifications; + +import android.os.Bundle; +import org.json.JSONException; +import org.json.JSONObject; + +public abstract class PageModifier { + + public static PageModifier fromJSON(JSONObject data) { + String name = data.optString("name"); + if(name.equals(CategoryModifier.MODIFIER_NAME)) { + return new CategoryModifier(data.optJSONObject("data")); + } + return null; + } + + protected String name; + protected JSONObject params; + + protected PageModifier(String name) { + this.name = name; + params = new JSONObject(); + } + + public abstract String doModification(String pageName, String pageContents); + + public JSONObject toJSON() { + JSONObject data = new JSONObject(); + try { + data.putOpt("name", name); + data.put("data", params); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return data; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/modifications/PostUploadActivity.java b/commons/src/main/java/org/wikimedia/commons/modifications/PostUploadActivity.java new file mode 100644 index 000000000..0ff97a6b4 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/modifications/PostUploadActivity.java @@ -0,0 +1,28 @@ +package org.wikimedia.commons.modifications; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import org.wikimedia.commons.HandlerService; +import org.wikimedia.commons.R; +import org.wikimedia.commons.UploadService; + +public class PostUploadActivity extends Activity { + public static String EXTRA_MEDIA_URI = "org.wikimedia.commons.modifications.PostUploadActivity.mediauri"; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_post_upload); + Uri mediaUri = getIntent().getParcelableExtra(EXTRA_MEDIA_URI); + ModifierSequence testSequence = new ModifierSequence(mediaUri); + testSequence.queueModifier(new CategoryModifier("Hello, World!")); + testSequence.setContentProviderClient(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY)); + testSequence.save(); + + } +} \ No newline at end of file