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

Reply via email to