mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
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
This commit is contained in:
parent
03277af6cc
commit
780af9d07d
17 changed files with 650 additions and 8 deletions
|
|
@ -33,6 +33,9 @@
|
||||||
android:name=".auth.LoginActivity"
|
android:name=".auth.LoginActivity"
|
||||||
android:theme="@style/NoTitle" >
|
android:theme="@style/NoTitle" >
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".modifications.PostUploadActivity"
|
||||||
|
/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ShareActivity"
|
android:name=".ShareActivity"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
|
|
@ -96,7 +99,19 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.content.SyncAdapter"
|
android:name="android.content.SyncAdapter"
|
||||||
android:resource="@xml/syncadapter" />
|
android:resource="@xml/contributions_sync_adapter" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".modifications.ModificationsSyncService"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action
|
||||||
|
android:name="android.content.SyncAdapter" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.content.SyncAdapter"
|
||||||
|
android:resource="@xml/modifications_sync_adapter" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|
@ -106,6 +121,13 @@
|
||||||
android:authorities="org.wikimedia.commons.contributions.contentprovider"
|
android:authorities="org.wikimedia.commons.contributions.contentprovider"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name=".modifications.ModificationsContentProvider"
|
||||||
|
android:label="@string/provider_modifications"
|
||||||
|
android:syncable="true"
|
||||||
|
android:authorities="org.wikimedia.commons.modifications.contentprovider"
|
||||||
|
android:exported="false">
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
8
commons/res/layout/activity_post_upload.xml
Normal file
8
commons/res/layout/activity_post_upload.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
|
|
||||||
<string name="share_upload_button">Upload</string>
|
<string name="share_upload_button">Upload</string>
|
||||||
<string name="multiple_share_base_title">Name this set</string>
|
<string name="multiple_share_base_title">Name this set</string>
|
||||||
|
<string name="provider_modifications">Modifications</string>
|
||||||
|
|
||||||
<plurals name="contributions_subtitle">
|
<plurals name="contributions_subtitle">
|
||||||
<item quantity="zero">No uploads yet</item>
|
<item quantity="zero">No uploads yet</item>
|
||||||
|
|
|
||||||
9
commons/res/xml/modifications_sync_adapter.xml
Normal file
9
commons/res/xml/modifications_sync_adapter.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:contentAuthority="org.wikimedia.commons.modifications.contentprovider"
|
||||||
|
android:accountType="org.wikimedia.commons"
|
||||||
|
android:supportsUploading="true"
|
||||||
|
android:userVisible="true"
|
||||||
|
android:isAlwaysSyncable="true"
|
||||||
|
/>
|
||||||
|
|
@ -14,6 +14,7 @@ import android.view.*;
|
||||||
|
|
||||||
import org.wikimedia.commons.contributions.*;
|
import org.wikimedia.commons.contributions.*;
|
||||||
import org.wikimedia.commons.auth.*;
|
import org.wikimedia.commons.auth.*;
|
||||||
|
import org.wikimedia.commons.modifications.PostUploadActivity;
|
||||||
|
|
||||||
|
|
||||||
public class ShareActivity extends AuthenticatedActivity {
|
public class ShareActivity extends AuthenticatedActivity {
|
||||||
|
|
@ -64,11 +65,13 @@ public class ShareActivity extends AuthenticatedActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Contribution contribution) {
|
protected void onPostExecute(Contribution contribution) {
|
||||||
super.onPostExecute(contribution);
|
super.onPostExecute(contribution);
|
||||||
|
Intent postUploadIntent = new Intent(ShareActivity.this, PostUploadActivity.class);
|
||||||
|
postUploadIntent.putExtra(PostUploadActivity.EXTRA_MEDIA_URI, contribution.getContentUri());
|
||||||
|
startActivity(postUploadIntent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import android.support.v4.app.NavUtils;
|
||||||
import org.wikimedia.commons.*;
|
import org.wikimedia.commons.*;
|
||||||
import org.wikimedia.commons.EventLog;
|
import org.wikimedia.commons.EventLog;
|
||||||
import org.wikimedia.commons.contributions.*;
|
import org.wikimedia.commons.contributions.*;
|
||||||
|
import org.wikimedia.commons.modifications.ModificationsContentProvider;
|
||||||
|
|
||||||
|
|
||||||
public class LoginActivity extends AccountAuthenticatorActivity {
|
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
|
// 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, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||||
|
ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||||
context.finish();
|
context.finish();
|
||||||
} else {
|
} else {
|
||||||
int response;
|
int response;
|
||||||
|
|
|
||||||
|
|
@ -297,8 +297,11 @@ public class Contribution extends Media {
|
||||||
onUpdate(db, from, to);
|
onUpdate(db, from, to);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
if(from == 3) {
|
||||||
onCreate(db);
|
// Do nothing
|
||||||
|
from++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,16 +45,27 @@ public class ContributionsContentProvider extends ContentProvider{
|
||||||
|
|
||||||
int uriType = uriMatcher.match(uri);
|
int uriType = uriMatcher.match(uri);
|
||||||
|
|
||||||
|
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||||
|
Cursor cursor;
|
||||||
|
|
||||||
switch(uriType) {
|
switch(uriType) {
|
||||||
case CONTRIBUTIONS:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown URI" + uri);
|
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);
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ import android.content.*;
|
||||||
import android.database.sqlite.*;
|
import android.database.sqlite.*;
|
||||||
|
|
||||||
import org.wikimedia.commons.contributions.*;
|
import org.wikimedia.commons.contributions.*;
|
||||||
|
import org.wikimedia.commons.modifications.ModifierSequence;
|
||||||
|
|
||||||
public class DBOpenHelper extends SQLiteOpenHelper{
|
public class DBOpenHelper extends SQLiteOpenHelper{
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "commons.db";
|
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) {
|
public DBOpenHelper(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
|
@ -17,10 +18,12 @@ public class DBOpenHelper extends SQLiteOpenHelper{
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(SQLiteDatabase sqLiteDatabase) {
|
public void onCreate(SQLiteDatabase sqLiteDatabase) {
|
||||||
Contribution.Table.onCreate(sqLiteDatabase);
|
Contribution.Table.onCreate(sqLiteDatabase);
|
||||||
|
ModifierSequence.Table.onCreate(sqLiteDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
|
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
|
||||||
Contribution.Table.onUpdate(sqLiteDatabase, from, to);
|
Contribution.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
|
ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<PageModifier> modifiers;
|
||||||
|
private Uri contentUri;
|
||||||
|
private ContentProviderClient client;
|
||||||
|
|
||||||
|
public ModifierSequence(Uri mediaUri) {
|
||||||
|
this.mediaUri = mediaUri;
|
||||||
|
modifiers = new ArrayList<PageModifier>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue