Brion VIBBER has submitted this change and it was merged. Change subject: Use a SyncProvider to sync all Campaigns ......................................................................
Use a SyncProvider to sync all Campaigns The SyncService constantly deletes and re-creates the campaigns in the background to make sure they are up to date FIXME: Handle deleted or renamed campaigns Change-Id: I5d03995ada219481ea38887a8ea6d59fa11d2ac8 --- M commons/AndroidManifest.xml M commons/res/layout/activity_campaigns.xml M commons/res/values/strings.xml A commons/res/xml/campaigns_sync_adapter.xml M commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java M commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java A commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java M commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java D commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java A commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java A commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java D commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java M commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java 13 files changed, 551 insertions(+), 136 deletions(-) Approvals: Brion VIBBER: Verified; Looks good to me, approved diff --git a/commons/AndroidManifest.xml b/commons/AndroidManifest.xml index f3cb11f..4e24f5d 100644 --- a/commons/AndroidManifest.xml +++ b/commons/AndroidManifest.xml @@ -106,6 +106,17 @@ android:resource="@xml/authenticator" /> </service> <service + android:name=".campaigns.CampaignsSyncService" + android:exported="true"> + <intent-filter> + <action + android:name="android.content.SyncAdapter" /> + </intent-filter> + <meta-data + android:name="android.content.SyncAdapter" + android:resource="@xml/campaigns_sync_adapter" /> + </service> + <service android:name=".contributions.ContributionsSyncService" android:exported="true"> <intent-filter> @@ -137,6 +148,13 @@ android:exported="false"> </provider> <provider + android:name=".campaigns.CampaignsContentProvider" + android:label="@string/provider_campaigns" + android:syncable="true" + android:authorities="org.wikimedia.commons.campaigns.contentprovider" + android:exported="false"> + </provider> + <provider android:name=".modifications.ModificationsContentProvider" android:label="@string/provider_modifications" android:syncable="true" diff --git a/commons/res/layout/activity_campaigns.xml b/commons/res/layout/activity_campaigns.xml index 197ccb2..cfac7c7 100644 --- a/commons/res/layout/activity_campaigns.xml +++ b/commons/res/layout/activity_campaigns.xml @@ -5,9 +5,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <fragment - android:id="@+id/campaignsListFragment" - class="org.wikimedia.commons.campaigns.CampaignsListFragment" + <ListView + android:id="@+id/campaignsList" android:layout_width="match_parent" android:layout_height="match_parent" /> diff --git a/commons/res/values/strings.xml b/commons/res/values/strings.xml index 80208b8..bac68d4 100644 --- a/commons/res/values/strings.xml +++ b/commons/res/values/strings.xml @@ -102,4 +102,5 @@ <string name="welcome_copyright_subtext">Avoid copyrighted materials you found from the Internet as well as images of posters, book covers, etc.</string> <string name="welcome_final_text">You think you got it?</string> <string name="welcome_final_button_text">Yes!</string> + <string name="provider_campaigns">Campaigns</string> </resources> diff --git a/commons/res/xml/campaigns_sync_adapter.xml b/commons/res/xml/campaigns_sync_adapter.xml new file mode 100644 index 0000000..778984c --- /dev/null +++ b/commons/res/xml/campaigns_sync_adapter.xml @@ -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.campaigns.contentprovider" + android:accountType="org.wikimedia.commons" + android:supportsUploading="false" + android:userVisible="true" + android:isAlwaysSyncable="true" + /> diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java index 0eeb1dc..3eced64 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java @@ -1,6 +1,10 @@ package org.wikimedia.commons.campaigns; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; @@ -17,16 +21,27 @@ private String defaultDescription; private JSONObject config; + private String body; + private boolean isParsed; + private String trackingCategory; + private String description; + private String title; public boolean isEnabled() { return enabled; } public String getAutoAddWikitext() { + if(!this.isParsed) { + this.parseConfig(); + } return autoAddWikitext; } public ArrayList<String> getAutoAddCategories() { + if(!this.isParsed) { + this.parseConfig(); + } return autoAddCategories; } @@ -35,20 +50,32 @@ } public String getOwnWorkLicenseDefault() { + if(!this.isParsed) { + this.parseConfig(); + } return ownWorkLicenseDefault; } public String getDefaultDescription() { + if(!this.isParsed) { + this.parseConfig(); + } return defaultDescription; } public JSONObject getConfig() { + if(!this.isParsed) { + this.parseConfig(); + } return config; } - public Campaign(String name, JSONObject config) { - this.config = config; - this.name = name; + private void parseConfig() { + try { + this.config = new JSONObject(body); + } catch (JSONException e) { + throw new RuntimeException(e); // because what else are you gonna do? + } if(config.has("autoAdd")) { this.autoAddWikitext = config.optJSONObject("autoAdd").optString("wikitext", null); if(config.optJSONObject("autoAdd").has("categories")) { @@ -59,5 +86,104 @@ } } } + this.title = config.optString("title", name); + this.description = config.optString("description", ""); + this.isParsed = true; + } + private Campaign(String name, String body, String trackingCategory) { + this.name = name; + this.body = body; + this.trackingCategory = trackingCategory; + } + + public ContentValues toContentValues() { + ContentValues cv = new ContentValues(); + cv.put(Table.COLUMN_NAME, this.getName()); + cv.put(Table.COLUMN_ENABLED, this.isEnabled() ? 1 : 0); + cv.put(Table.COLUMN_TITLE, this.getTitle()); + cv.put(Table.COLUMN_DESCRIPTION, this.getDescription()); + cv.put(Table.COLUMN_TRACKING_CATEGORY, this.getTrackingCategory()); + cv.put(Table.COLUMN_BODY, this.body); + return cv; + } + + public static Campaign parse(String name, String body, String trackingCategory) { + Campaign c = new Campaign(name, body, trackingCategory); + c.parseConfig(); + return c; + } + + public static Campaign fromCursor(Cursor cursor) { + String name = cursor.getString(1); + Boolean enabled = cursor.getInt(2) == 1; + String title = cursor.getString(3); + String description = cursor.getString(4); + String trackingCategory = cursor.getString(5); + String body = cursor.getString(6); + Campaign c = new Campaign(name, body, trackingCategory); + c.title = title; + c.description = description; + c.enabled = enabled; + return c; + } + + public String getTrackingCategory() { + return trackingCategory; + } + + public String getDescription() { + return description; + } + + public String getTitle() { + return title; + } + + public static class Table { + public static final String TABLE_NAME = "campaigns"; + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_ENABLED = "enabled"; + public static final String COLUMN_TITLE = "title"; + public static final String COLUMN_DESCRIPTION = "description"; + public static final String COLUMN_TRACKING_CATEGORY = "tracking_category"; + public static final String COLUMN_BODY = "body"; + + // 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_ENABLED, + COLUMN_TITLE, + COLUMN_DESCRIPTION, + COLUMN_DESCRIPTION, + COLUMN_TRACKING_CATEGORY, + COLUMN_BODY + }; + + + private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" + + "_id INTEGER PRIMARY KEY," + + "name STRING," + + "enabled INTEGER," + + "title STRING," + + "description STRING," + + "tracking_category STRING," + + "body STRING" + + ");"; + + + public static void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_STATEMENT); + } + + public static void onUpdate(SQLiteDatabase db, int from, int to) { + if(to <= 6) { + onCreate(db); + return; + } + return; + } } } diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java index d2ec068..7b84898 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java @@ -1,15 +1,44 @@ package org.wikimedia.commons.campaigns; import android.app.Activity; +import android.database.Cursor; import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.widget.ListView; import com.actionbarsherlock.app.SherlockFragmentActivity; import org.wikimedia.commons.R; -public class CampaignActivity extends SherlockFragmentActivity { +public class CampaignActivity + extends SherlockFragmentActivity + implements LoaderManager.LoaderCallbacks<Cursor> { + + private ListView campaignsListView; + private CampaignsListAdapter campaignsListAdapter; + private Cursor allCampaigns; + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_campaigns); + campaignsListView = (ListView) findViewById(R.id.campaignsList); + getSupportLoaderManager().initLoader(0, null, this); } + public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { + return new CursorLoader(this, CampaignsContentProvider.BASE_URI, Campaign.Table.ALL_FIELDS, "", null, ""); + } + public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { + if(campaignsListAdapter == null) { + campaignsListAdapter = new CampaignsListAdapter(this, cursor, 0); + campaignsListView.setAdapter(campaignsListAdapter); + } else { + campaignsListAdapter.swapCursor(cursor); + } + } + + public void onLoaderReset(Loader<Cursor> cursorLoader) { + campaignsListAdapter.swapCursor(null); + } } \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java new file mode 100644 index 0000000..80bd972 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java @@ -0,0 +1,206 @@ +package org.wikimedia.commons.campaigns; + +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; + +public class CampaignsContentProvider extends ContentProvider{ + + private static final int CAMPAIGNS = 1; + private static final int CAMPAIGNS_ID = 2; + + public static final String AUTHORITY = "org.wikimedia.commons.campaigns.contentprovider"; + private static final String BASE_PATH = "campiagns"; + + 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, CAMPAIGNS); + uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CAMPAIGNS_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(Campaign.Table.TABLE_NAME); + + int uriType = uriMatcher.match(uri); + + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + Cursor cursor; + + switch(uriType) { + case CAMPAIGNS: + cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + break; + case CAMPAIGNS_ID: + cursor = queryBuilder.query(db, + Campaign.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 CAMPAIGNS: + sqlDB.beginTransaction(); + // if the campaign already exists, rip it out and then re-insert + if(campaignExists(sqlDB, contentValues)) { + sqlDB.delete( + Campaign.Table.TABLE_NAME, + Campaign.Table.COLUMN_NAME + " = ?", + new String[]{contentValues.getAsString(Campaign.Table.COLUMN_NAME)} + ); + } + id = sqlDB.insert(Campaign.Table.TABLE_NAME, null, contentValues); + sqlDB.endTransaction(); + 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 rows = 0; + int uriType = uriMatcher.match(uri); + + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + + switch(uriType) { + case CAMPAIGNS_ID: + rows = db.delete(Campaign.Table.TABLE_NAME, + "_id = ?", + new String[] { uri.getLastPathSegment() } + ); + break; + default: + throw new IllegalArgumentException("Unknown URI" + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return rows; + } + + private boolean campaignExists(SQLiteDatabase db, ContentValues campaign) { + Cursor cr = db.query( + Campaign.Table.TABLE_NAME, + new String[]{Campaign.Table.COLUMN_NAME}, + Campaign.Table.COLUMN_NAME + " = ?", + new String[]{campaign.getAsString(Campaign.Table.COLUMN_NAME)}, + "", "", "" + ); + return cr != null && cr.getCount() != 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 CAMPAIGNS: + for(ContentValues value: values) { + Log.d("Commons", "Inserting! " + value.toString()); + // if the campaign already exists, rip it out and then re-insert + if(campaignExists(sqlDB, value)) { + sqlDB.delete( + Campaign.Table.TABLE_NAME, + Campaign.Table.COLUMN_NAME + " = ?", + new String[]{value.getAsString(Campaign.Table.COLUMN_NAME)} + ); + } + sqlDB.insert(Campaign.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 CAMPAIGNS: + rowsUpdated = sqlDB.update(Campaign.Table.TABLE_NAME, + contentValues, + selection, + selectionArgs); + break; + case CAMPAIGNS_ID: + int id = Integer.valueOf(uri.getLastPathSegment()); + + if (TextUtils.isEmpty(selection)) { + rowsUpdated = sqlDB.update(Campaign.Table.TABLE_NAME, + contentValues, + Campaign.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/campaigns/CampaignsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java index f1bc1fb..b43d383 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java @@ -1,54 +1,47 @@ package org.wikimedia.commons.campaigns; import android.app.Activity; -import android.view.*; -import android.widget.*; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.support.v4.widget.CursorAdapter; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.actionbarsherlock.app.SherlockFragment; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.MediaWikiImageView; import org.wikimedia.commons.R; +import org.wikimedia.commons.Utils; +import org.wikimedia.commons.campaigns.Campaign; -import java.util.ArrayList; +class CampaignsListAdapter extends CursorAdapter { -public class CampaignsListAdapter extends BaseAdapter { - private ArrayList<Campaign> campaigns; + private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();; private Activity activity; - public CampaignsListAdapter(Activity activity, ArrayList<Campaign> campaigns) { - this.campaigns = campaigns; + public CampaignsListAdapter(Activity activity, Cursor c, int flags) { + super(activity, c, flags); this.activity = activity; } - public ArrayList<Campaign> getCampaigns() { - return campaigns; + @Override + public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { + View parent = activity.getLayoutInflater().inflate(android.R.layout.simple_list_item_1, viewGroup, false); + return parent; } - public void setCampaigns(ArrayList<Campaign> campaigns) { - this.campaigns = campaigns; + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView campaignName = (TextView)view.findViewById(android.R.id.text1); + + Campaign campaign = Campaign.fromCursor(cursor); + + campaignName.setText(campaign.getTitle()); } - public int getCount() { - if(campaigns == null) { - return 0; - } - return campaigns.size(); - } - - public Object getItem(int i) { - return campaigns.get(i); - } - - public long getItemId(int i) { - return i; - } - - public View getView(int i, View view, ViewGroup viewGroup) { - if(view == null) { - view = activity.getLayoutInflater().inflate(R.layout.layout_campaign_item, null); - } - - TextView campaignName = (TextView)view.findViewById(R.id.campaignItemName); - - Campaign campaign = campaigns.get(i); - - campaignName.setText(campaign.getName()); - return view; - } } diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java deleted file mode 100644 index 51a2f4f..0000000 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.wikimedia.commons.campaigns; - -import android.os.*; -import android.view.*; -import android.widget.*; -import com.actionbarsherlock.app.SherlockFragment; -import org.wikimedia.commons.*; - -import java.util.*; - -public class CampaignsListFragment extends SherlockFragment { - private ArrayList<Campaign> campaigns; - private CampaignsListAdapter listAdapter; - private GridView campaignsListView; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_campaigns, container, false); - campaignsListView = (GridView)view.findViewById(R.id.campaignsList); - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - listAdapter = new CampaignsListAdapter(getActivity(), campaigns); - campaignsListView.setAdapter(listAdapter); - Utils.executeAsyncTask(new FetchCampaignsTask(getActivity()){ - @Override - protected void onPostExecute(ArrayList<Campaign> campaigns) { - super.onPostExecute(campaigns); - CampaignsListFragment.this.campaigns = campaigns; - listAdapter.setCampaigns(campaigns); - listAdapter.notifyDataSetChanged(); - } - }); - } -} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java new file mode 100644 index 0000000..9365028 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java @@ -0,0 +1,95 @@ +package org.wikimedia.commons.campaigns; + +import android.accounts.Account; +import android.content.*; +import android.database.Cursor; +import android.os.Bundle; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import org.mediawiki.api.ApiResult; +import org.mediawiki.api.MWApi; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.Utils; +import org.wikimedia.commons.contributions.Contribution; +import org.wikimedia.commons.contributions.ContributionsContentProvider; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; + + +public class CampaignsSyncAdapter extends AbstractThreadedSyncAdapter { + private static int COMMIT_THRESHOLD = 10; + public CampaignsSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + private int getLimit() { + return 500; // FIXME: Parameterize! + } + + @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! + String user = account.name; + MWApi api = CommonsApplication.createMWApi(); + ApiResult result; + Boolean done = false; + String queryContinue = null; + while(!done) { + + try { + MWApi.RequestBuilder builder = api.action("query") + .param("list", "allcampaigns") + // Disabled, since we want to modify local state if the campaign was disabled + // FIXME: To be more effecient, delete the disabled campaigns locally + //.param("ucenabledonly", "true") + .param("uclimit", getLimit()); + if(!TextUtils.isEmpty(queryContinue)) { + builder.param("uccontinue", queryContinue); + } + result = builder.get(); + } catch (IOException e) { + // There isn't really much we can do, eh? + // FIXME: Perhaps add EventLogging? + syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs + Log.d("Commons", "Syncing failed due to " + e.toString()); + return; + } + + ArrayList<ApiResult> campaigns = result.getNodes("/api/query/allcampaigns/campaign"); + Log.d("Commons", campaigns.size() + " results!"); + ArrayList<ContentValues> campaignValues = new ArrayList<ContentValues>(); + for(ApiResult campaignItem: campaigns) { + String name = campaignItem.getString("@name"); + String body = campaignItem.getString("."); + Log.d("Commons", "Campaign body is " + body); + String trackingCat = campaignItem.getString("@trackingCategory"); + Campaign campaign = Campaign.parse(name, body, trackingCat); + campaignValues.add(campaign.toContentValues()); + + if(campaignValues.size() % COMMIT_THRESHOLD == 0) { + try { + contentProviderClient.bulkInsert(CampaignsContentProvider.BASE_URI, campaignValues.toArray(new ContentValues[]{})); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + campaignValues.clear(); + } + } + + if(campaignValues.size() != 0) { + try { + contentProviderClient.bulkInsert(CampaignsContentProvider.BASE_URI, campaignValues.toArray(new ContentValues[]{})); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + queryContinue = result.getString("/api/query-continue/allcampaigns/@uccontinue"); + if(TextUtils.isEmpty(queryContinue)) { + done = true; + } + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java new file mode 100644 index 0000000..d1975cc --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java @@ -0,0 +1,27 @@ +package org.wikimedia.commons.campaigns; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import org.wikimedia.commons.contributions.ContributionsSyncAdapter; + +public class CampaignsSyncService extends Service { + + private static final Object sSyncAdapterLock = new Object(); + + private static CampaignsSyncAdapter sSyncAdapter = null; + + @Override + public void onCreate() { + synchronized (sSyncAdapterLock) { + if (sSyncAdapter == null) { + sSyncAdapter = new CampaignsSyncAdapter(getApplicationContext(), true); + } + } + } + + @Override + public IBinder onBind(Intent intent) { + return sSyncAdapter.getSyncAdapterBinder(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java b/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java deleted file mode 100644 index 8a822de..0000000 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.wikimedia.commons.campaigns; - -import android.content.Context; -import android.os.AsyncTask; -import android.util.Log; -import org.json.JSONException; -import org.json.JSONObject; -import org.mediawiki.api.ApiResult; -import org.mediawiki.api.MWApi; -import org.wikimedia.commons.CommonsApplication; -import org.wikimedia.commons.Utils; - -import java.io.IOException; -import java.util.ArrayList; - -public class FetchCampaignsTask extends AsyncTask<Void, Void, ArrayList<Campaign>>{ - - private Context context; - private MWApi api; - - public FetchCampaignsTask(Context context) { - this.context = context; - this.api = ((CommonsApplication)context.getApplicationContext()).getApi(); - } - - @Override - protected ArrayList<Campaign> doInBackground(Void... voids) { - ArrayList<Campaign> campaigns = new ArrayList<Campaign>(); - ApiResult result; - try { - result = api.action("query").param("prop", "revisions") - .param("rvprop", "content").param("generator", "allpages") - .param("gapnamespace", 460).param("gaplimit", 500).get(); //FIXME: Actually paginate! - } catch (IOException e) { - throw new RuntimeException(e); // FIXME: Do something else ;) - } - ArrayList<ApiResult> pageNodes = result.getNodes("/api/query/pages/page"); - for(ApiResult pageNode : pageNodes) { - Log.d("Commons", Utils.getStringFromDOM(pageNode.getDocument())); - String configString = pageNode.getString("revisions/rev"); - String pageName = pageNode.getString("@title").split(":")[1]; - JSONObject config; - try { - config = new JSONObject(configString); - } catch (JSONException e) { - throw new RuntimeException(e); // NEVER HAPPENS! - } - campaigns.add(new Campaign(pageName, config)); - } - return campaigns; - } -} 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 af68065..a8560a9 100644 --- a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java +++ b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java @@ -3,6 +3,7 @@ import android.content.*; import android.database.sqlite.*; +import org.wikimedia.commons.campaigns.Campaign; import org.wikimedia.commons.category.Category; import org.wikimedia.commons.contributions.*; import org.wikimedia.commons.modifications.ModifierSequence; @@ -21,6 +22,7 @@ Contribution.Table.onCreate(sqLiteDatabase); ModifierSequence.Table.onCreate(sqLiteDatabase); Category.Table.onCreate(sqLiteDatabase); + Campaign.Table.onCreate(sqLiteDatabase); } @Override @@ -28,5 +30,6 @@ Contribution.Table.onUpdate(sqLiteDatabase, from, to); ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to); Category.Table.onUpdate(sqLiteDatabase, from, to); + Campaign.Table.onUpdate(sqLiteDatabase, from, to); } } -- To view, visit https://gerrit.wikimedia.org/r/83045 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I5d03995ada219481ea38887a8ea6d59fa11d2ac8 Gerrit-PatchSet: 4 Gerrit-Project: apps/android/commons Gerrit-Branch: campaigns Gerrit-Owner: Yuvipanda <yuvipa...@gmail.com> Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org> Gerrit-Reviewer: Yuvipanda <yuvipa...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits