Dbrant has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/391048 )

Change subject: Sync.
......................................................................

Sync.

Change-Id: Ia82c4c5288c28ae83f7a2d56d603cae14e86adb2
---
M app/build.gradle
M app/src/main/AndroidManifest.xml
M app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
M app/src/main/java/org/wikipedia/csrf/CsrfTokenClient.java
M app/src/main/java/org/wikipedia/database/Database.java
M app/src/main/java/org/wikipedia/database/contract/ReadingListContract.java
M app/src/main/java/org/wikipedia/dataclient/ServiceError.java
M app/src/main/java/org/wikipedia/dataclient/mwapi/MwServiceError.java
M app/src/main/java/org/wikipedia/dataclient/okhttp/HttpStatusException.java
M app/src/main/java/org/wikipedia/dataclient/restbase/RbServiceError.java
M app/src/main/java/org/wikipedia/login/LoginClient.java
M app/src/main/java/org/wikipedia/notifications/NotificationClient.java
M app/src/main/java/org/wikipedia/readinglist/database/ReadingListRow.java
M app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
A app/src/main/java/org/wikipedia/readinglist/sync/ReadingListClient.java
A app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSyncService.java
M app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
A app/src/main/java/org/wikipedia/readinglist/sync/SyncedReadingLists.java
M 
app/src/main/java/org/wikipedia/useroption/dataclient/UserOptionDataClient.java
M app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
20 files changed, 436 insertions(+), 46 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia 
refs/changes/48/391048/1

diff --git a/app/build.gradle b/app/build.gradle
index f5daefc..ef1895c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -261,6 +261,7 @@
 }
 
 addSyncContentProviderAuthority 'useroption', 'user_option'
+addSyncContentProviderAuthority 'readinglists', 'reading_lists'
 
 private void addSyncContentProviderAuthority(String path, String name) {
     android.productFlavors.all { ProductFlavor flavor ->
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8448bf2..d2dc671 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -253,6 +253,13 @@
             android:label="@string/user_option_sync_label" />
 
         <provider
+            android:authorities="@string/reading_lists_authority"
+            android:name=".database.ReadingListsContentProvider"
+            android:exported="false"
+            android:syncable="true"
+            android:label="@string/user_option_sync_label" />
+
+        <provider
             android:name="android.support.v4.content.FileProvider"
             android:authorities="${applicationId}.fileprovider"
             android:exported="false"
@@ -333,6 +340,20 @@
         </service>
 
         <service
+            android:name=".readinglist.sync.ReadingListSyncService"
+            android:exported="false">
+
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.content.SyncAdapter"
+                android:resource="@xml/reading_list_sync_adapter" />
+
+        </service>
+
+        <service
             android:name=".auth.AuthenticatorService"
             android:exported="false">
 
diff --git a/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java 
b/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
index d00f6b9..96449aa 100644
--- a/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
+++ b/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
@@ -18,7 +18,7 @@
 import org.wikipedia.login.LoginActivity;
 
 public class WikimediaAuthenticator extends AbstractAccountAuthenticator {
-    private static final String[] SYNC_AUTHORITIES = 
{BuildConfig.USER_OPTION_AUTHORITY};
+    private static final String[] SYNC_AUTHORITIES = 
{BuildConfig.USER_OPTION_AUTHORITY, BuildConfig.READING_LISTS_AUTHORITY};
 
     @NonNull private final Context context;
 
diff --git a/app/src/main/java/org/wikipedia/csrf/CsrfTokenClient.java 
b/app/src/main/java/org/wikipedia/csrf/CsrfTokenClient.java
index d288167..5ee0b88 100644
--- a/app/src/main/java/org/wikipedia/csrf/CsrfTokenClient.java
+++ b/app/src/main/java/org/wikipedia/csrf/CsrfTokenClient.java
@@ -4,13 +4,17 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
+import android.widget.Toast;
 
+import org.wikipedia.R;
+import org.wikipedia.WikipediaApp;
 import org.wikipedia.auth.AccountUtil;
 import org.wikipedia.dataclient.SharedPreferenceCookieManager;
 import org.wikipedia.dataclient.WikiSite;
 import org.wikipedia.dataclient.mwapi.MwException;
 import org.wikipedia.dataclient.mwapi.MwQueryResponse;
 import org.wikipedia.dataclient.retrofit.MwCachedService;
+import org.wikipedia.dataclient.retrofit.RetrofitFactory;
 import org.wikipedia.dataclient.retrofit.WikiCachedService;
 import org.wikipedia.login.LoginClient;
 import org.wikipedia.login.LoginResult;
@@ -20,12 +24,26 @@
 
 import retrofit2.Call;
 import retrofit2.Response;
+import retrofit2.Retrofit;
 import retrofit2.http.GET;
 
 public class CsrfTokenClient {
     static final String ANON_TOKEN = "+\\";
     private static final int MAX_RETRIES = 1;
-    @NonNull private final WikiCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
+
+
+
+    // TODO!!!!!!!!!!!!!! Restore to uncommented version when ready.
+    //@NonNull private final WikiCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
+    @NonNull private final WikiCachedService<Service> cachedService = new 
MwCachedService<Service>(Service.class) {
+        @NonNull @Override protected Retrofit create() {
+            return RetrofitFactory.newInstance(new 
WikiSite("https://readinglists.wmflabs.org";));
+        }
+    };
+    //--------------------------------------------------------------
+
+
+
     @NonNull private final WikiSite csrfWikiSite;
     @NonNull private final WikiSite loginWikiSite;
     private int retries = 0;
@@ -129,6 +147,38 @@
                 });
     }
 
+    @NonNull public String getTokenBlocking() throws Throwable {
+        String token = "";
+        Service service = cachedService.service(csrfWikiSite);
+
+        for (int retries = 0; retries < 2; retries++) {
+            try {
+                if (retries > 0) {
+                    // Log in explicitly
+                    new LoginClient().loginBlocking(loginWikiSite, 
AccountUtil.getUserName(),
+                            AccountUtil.getPassword(), "");
+                }
+
+                Response<MwQueryResponse> response = 
service.request().execute();
+                if (response.body() == null || !response.body().success()
+                        || 
TextUtils.isEmpty(response.body().query().csrfToken())) {
+
+                }
+                token = response.body().query().csrfToken();
+                if (AccountUtil.isLoggedIn() && token.equals(ANON_TOKEN)) {
+                    throw new RuntimeException("App believes we're logged in, 
but got anonymous token.");
+                }
+                break;
+            } catch (Throwable t) {
+                L.w(t);
+            }
+        }
+        if (TextUtils.isEmpty(token) || token.equals(ANON_TOKEN)) {
+            throw new IOException("Invalid token, or login failure.");
+        }
+        return token;
+    }
+
     @VisibleForTesting @NonNull Call<MwQueryResponse> requestToken(@NonNull 
Service service,
                                                                    @NonNull 
final Callback cb) {
         Call<MwQueryResponse> call = service.request();
@@ -160,6 +210,23 @@
         void twoFactorPrompt();
     }
 
+    public static class DefaultCallback implements Callback {
+        @Override
+        public void success(@NonNull String token) {
+        }
+
+        @Override
+        public void failure(@NonNull Throwable caught) {
+            L.e(caught);
+        }
+
+        @Override
+        public void twoFactorPrompt() {
+            Toast.makeText(WikipediaApp.getInstance(),
+                    R.string.login_2fa_other_workflow_error_msg, 
Toast.LENGTH_LONG).show();
+        }
+    }
+
     private interface RetryCallback {
         void retry();
     }
diff --git a/app/src/main/java/org/wikipedia/database/Database.java 
b/app/src/main/java/org/wikipedia/database/Database.java
index 34e8098..b7121a5 100644
--- a/app/src/main/java/org/wikipedia/database/Database.java
+++ b/app/src/main/java/org/wikipedia/database/Database.java
@@ -16,7 +16,7 @@
 
 public class Database extends SQLiteOpenHelper {
     private static final String DATABASE_NAME = "wikipedia.db";
-    private static final int DATABASE_VERSION = 17;
+    private static final int DATABASE_VERSION = 18;
 
     private final DatabaseTable<?>[] databaseTables = {
             HistoryEntry.DATABASE_TABLE,
diff --git 
a/app/src/main/java/org/wikipedia/database/contract/ReadingListContract.java 
b/app/src/main/java/org/wikipedia/database/contract/ReadingListContract.java
index b924105..2c99d85 100644
--- a/app/src/main/java/org/wikipedia/database/contract/ReadingListContract.java
+++ b/app/src/main/java/org/wikipedia/database/contract/ReadingListContract.java
@@ -25,9 +25,10 @@
         LongColumn MTIME = new LongColumn(TABLE, "readingListMtime", "integer 
not null");
         LongColumn ATIME = new LongColumn(TABLE, "readingListAtime", "integer 
not null");
         StrColumn DESCRIPTION = new StrColumn(TABLE, "readingListDescription", 
"text");
+        LongColumn REMOTE_ID = new LongColumn(TABLE, "readingListServerId", 
"integer not null default -1");
 
         String[] SELECTION = DbUtil.qualifiedNames(KEY);
-        String[] ALL = DbUtil.qualifiedNames(ID, KEY, TITLE, MTIME, ATIME, 
DESCRIPTION);
+        String[] ALL = DbUtil.qualifiedNames(ID, KEY, TITLE, MTIME, ATIME, 
DESCRIPTION, REMOTE_ID);
     }
 
     interface List extends Col {
diff --git a/app/src/main/java/org/wikipedia/dataclient/ServiceError.java 
b/app/src/main/java/org/wikipedia/dataclient/ServiceError.java
index f703338..55dfff5 100644
--- a/app/src/main/java/org/wikipedia/dataclient/ServiceError.java
+++ b/app/src/main/java/org/wikipedia/dataclient/ServiceError.java
@@ -1,10 +1,12 @@
 package org.wikipedia.dataclient;
 
+import android.support.annotation.NonNull;
+
 /**
  * The API reported an error in the payload.
  */
 public interface ServiceError {
-    String getTitle();
+    @NonNull String getTitle();
 
-    String getDetails();
+    @NonNull String getDetails();
 }
diff --git 
a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwServiceError.java 
b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwServiceError.java
index 24ce4a0..b251fb8 100644
--- a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwServiceError.java
+++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwServiceError.java
@@ -19,12 +19,12 @@
     @SuppressWarnings("unused") @Nullable private String docref;
     @SuppressWarnings("unused") @NonNull private List<Message> messages = 
Collections.emptyList();
 
-    @Override @Nullable public String getTitle() {
-        return code;
+    @Override @NonNull public String getTitle() {
+        return StringUtils.defaultString(code);
     }
 
-    @Override @Nullable public String getDetails() {
-        return info;
+    @Override @NonNull public String getDetails() {
+        return StringUtils.defaultString(info);
     }
 
     @Nullable public String getDocRef() {
diff --git 
a/app/src/main/java/org/wikipedia/dataclient/okhttp/HttpStatusException.java 
b/app/src/main/java/org/wikipedia/dataclient/okhttp/HttpStatusException.java
index fbc3672..92c3f7a 100644
--- a/app/src/main/java/org/wikipedia/dataclient/okhttp/HttpStatusException.java
+++ b/app/src/main/java/org/wikipedia/dataclient/okhttp/HttpStatusException.java
@@ -1,6 +1,11 @@
 package org.wikipedia.dataclient.okhttp;
 
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.wikipedia.dataclient.ServiceError;
+import org.wikipedia.dataclient.restbase.RbServiceError;
+import org.wikipedia.util.log.L;
 
 import java.io.IOException;
 
@@ -9,18 +14,35 @@
 public class HttpStatusException extends IOException {
     private final int code;
     private final String url;
+    @Nullable private ServiceError serviceError;
 
     public HttpStatusException(@NonNull Response rsp) {
         this.code = rsp.code();
         this.url = rsp.request().url().uri().toString();
+        try {
+            if (rsp.body() != null && rsp.body().contentType() != null
+                    && rsp.body().contentType().toString().contains("json")) {
+                serviceError = RbServiceError.create(rsp.body().string());
+            }
+        } catch (Exception e) {
+            L.e(e);
+        }
     }
 
     public int code() {
         return code;
     }
 
+    public ServiceError serviceError() {
+        return serviceError;
+    }
+
     @Override
     public String getMessage() {
-        return "Code: " + Integer.toString(code) + ", URL: " + url;
+        String str = "Code: " + Integer.toString(code) + ", URL: " + url;
+        if (serviceError != null) {
+            str += ", title: " + serviceError.getTitle() + ", detail: " + 
serviceError.getDetails();
+        }
+        return str;
     }
 }
diff --git 
a/app/src/main/java/org/wikipedia/dataclient/restbase/RbServiceError.java 
b/app/src/main/java/org/wikipedia/dataclient/restbase/RbServiceError.java
index 8b957aa..31f8d20 100644
--- a/app/src/main/java/org/wikipedia/dataclient/restbase/RbServiceError.java
+++ b/app/src/main/java/org/wikipedia/dataclient/restbase/RbServiceError.java
@@ -2,6 +2,7 @@
 
 import android.support.annotation.NonNull;
 
+import org.apache.commons.lang3.StringUtils;
 import org.wikipedia.dataclient.ServiceError;
 import org.wikipedia.json.GsonUnmarshaller;
 import org.wikipedia.model.BaseModel;
@@ -21,12 +22,14 @@
     }
 
     @Override
+    @NonNull
     public String getTitle() {
-        return title;
+        return StringUtils.defaultString(title);
     }
 
     @Override
+    @NonNull
     public String getDetails() {
-        return detail;
+        return StringUtils.defaultString(detail);
     }
 }
diff --git a/app/src/main/java/org/wikipedia/login/LoginClient.java 
b/app/src/main/java/org/wikipedia/login/LoginClient.java
index 29bb794..359cc86 100644
--- a/app/src/main/java/org/wikipedia/login/LoginClient.java
+++ b/app/src/main/java/org/wikipedia/login/LoginClient.java
@@ -3,16 +3,20 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
+import android.widget.Toast;
 
 import com.google.gson.annotations.SerializedName;
 
 import org.apache.commons.lang3.StringUtils;
 import org.wikipedia.Constants;
+import org.wikipedia.R;
+import org.wikipedia.WikipediaApp;
 import org.wikipedia.dataclient.WikiSite;
 import org.wikipedia.dataclient.mwapi.MwException;
 import org.wikipedia.dataclient.mwapi.MwQueryResponse;
 import org.wikipedia.dataclient.mwapi.MwServiceError;
 import org.wikipedia.dataclient.retrofit.MwCachedService;
+import org.wikipedia.dataclient.retrofit.RetrofitFactory;
 import org.wikipedia.dataclient.retrofit.WikiCachedService;
 import org.wikipedia.util.log.L;
 
@@ -24,6 +28,7 @@
 import retrofit2.Call;
 import retrofit2.Callback;
 import retrofit2.Response;
+import retrofit2.Retrofit;
 import retrofit2.http.Field;
 import retrofit2.http.FormUrlEncoded;
 import retrofit2.http.POST;
@@ -32,8 +37,19 @@
  * Responsible for making login related requests to the server.
  */
 public class LoginClient {
-    @NonNull private final WikiCachedService<Service> cachedService
-            = new MwCachedService<>(Service.class);
+
+
+
+    // TODO!!!!!!!!!!!!!! Restore to uncommented version when ready.
+    //@NonNull private final WikiCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
+    @NonNull private final WikiCachedService<Service> cachedService = new 
MwCachedService<Service>(Service.class) {
+        @NonNull @Override protected Retrofit create() {
+            return RetrofitFactory.newInstance(new 
WikiSite("https://readinglists.wmflabs.org";));
+        }
+    };
+    //--------------------------------------------------------------
+
+
 
     @Nullable private Call<MwQueryResponse> tokenCall;
     @Nullable private Call<LoginResponse> loginCall;
@@ -109,6 +125,44 @@
         });
     }
 
+    public void loginBlocking(@NonNull final WikiSite wiki, @NonNull final 
String userName,
+                              @NonNull final String password, @Nullable final 
String twoFactorCode) throws Throwable {
+        Response<MwQueryResponse> tokenResponse = 
cachedService.service(wiki).requestLoginToken().execute();
+        if (tokenResponse.body() == null || !tokenResponse.body().success()
+                || 
TextUtils.isEmpty(tokenResponse.body().query().loginToken())) {
+            throw new IOException("Unexpected response when getting login 
token.");
+        }
+        String loginToken = tokenResponse.body().query().loginToken();
+
+        Call<LoginResponse> loginCall = 
StringUtils.defaultIfEmpty(twoFactorCode, "").isEmpty()
+                ? cachedService.service(wiki).logIn(userName, password, 
loginToken, Constants.WIKIPEDIA_URL)
+                : cachedService.service(wiki).logIn(userName, password, 
twoFactorCode, loginToken, true);
+        Response<LoginResponse> response = loginCall.execute();
+        LoginResponse loginResponse = response.body();
+        if (loginResponse == null) {
+            throw new IOException("Unexpected response when logging in.");
+        }
+        LoginResult loginResult = loginResponse.toLoginResult(wiki, password);
+        if (loginResult == null) {
+            throw new IOException("Unexpected response when logging in.");
+        }
+        if ("UI".equals(loginResult.getStatus())) {
+            if (loginResult instanceof LoginOAuthResult) {
+
+                // TODO: Find a better way to boil up the warning about 2FA
+                Toast.makeText(WikipediaApp.getInstance(),
+                        R.string.login_2fa_other_workflow_error_msg, 
Toast.LENGTH_LONG).show();
+
+                throw new LoginFailedException(loginResult.getMessage());
+
+            } else {
+                throw new LoginFailedException(loginResult.getMessage());
+            }
+        } else if (!loginResult.pass() || 
TextUtils.isEmpty(loginResult.getUserName())) {
+            throw new LoginFailedException(loginResult.getMessage());
+        }
+    }
+
     private void getExtendedInfo(@NonNull final WikiSite wiki, @NonNull String 
userName,
                                  @NonNull final LoginResult loginResult, 
@NonNull final LoginCallback cb) {
         UserExtendedInfoClient infoClient = new UserExtendedInfoClient();
diff --git 
a/app/src/main/java/org/wikipedia/notifications/NotificationClient.java 
b/app/src/main/java/org/wikipedia/notifications/NotificationClient.java
index 3974a2b..1fe13f1 100644
--- a/app/src/main/java/org/wikipedia/notifications/NotificationClient.java
+++ b/app/src/main/java/org/wikipedia/notifications/NotificationClient.java
@@ -78,7 +78,7 @@
 
     public void markRead(List<Notification> notifications) {
         final String idListStr = TextUtils.join("|", notifications);
-        editTokenClient.request(new CsrfTokenClient.Callback() {
+        editTokenClient.request(new CsrfTokenClient.DefaultCallback() {
             @Override
             public void success(@NonNull String token) {
                 requestMarkRead(service, token, idListStr).enqueue(new 
retrofit2.Callback<MwQueryResponse>() {
@@ -92,16 +92,6 @@
                         L.e(t);
                     }
                 });
-            }
-
-            @Override
-            public void failure(@NonNull Throwable t) {
-                L.e(t);
-            }
-
-            @Override
-            public void twoFactorPrompt() {
-                // TODO: warn user.
             }
         });
     }
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListRow.java 
b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListRow.java
index 7044e14..bcd4965 100644
--- a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListRow.java
+++ b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListRow.java
@@ -17,6 +17,7 @@
     private final long mtime;
     private long atime;
     @Nullable private String description;
+    private long remoteId = -1;
 
     public static Builder<?> builder() {
         //noinspection rawtypes
@@ -56,12 +57,17 @@
         this.description = description;
     }
 
+    public long remoteId() {
+        return remoteId;
+    }
+
     protected ReadingListRow(@NonNull Builder<?> builder) {
         key = builder.key;
         title = builder.title;
         mtime = builder.mtime;
         atime = builder.atime;
         description = builder.description;
+        remoteId = builder.remoteId;
     }
 
     @SuppressWarnings("unchecked")
@@ -71,13 +77,15 @@
         private Long mtime;
         private Long atime;
         private String description;
+        private Long remoteId = -1L;
 
         public Clazz copy(@NonNull ReadingListRow copy) {
             return key(copy.key)
                     .title(copy.title)
                     .mtime(copy.mtime)
                     .atime(copy.atime)
-                    .description(copy.description);
+                    .description(copy.description)
+                    .remoteId(copy.remoteId);
         }
 
         public Clazz key(@NonNull String key) {
@@ -105,6 +113,11 @@
             return (Clazz) this;
         }
 
+        public Clazz remoteId(long remoteId) {
+            this.remoteId = remoteId;
+            return (Clazz) this;
+        }
+
         public ReadingListRow build() {
             validate();
             return new ReadingListRow(this);
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java 
b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
index 1bd5a8a..9be020a 100644
--- a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
+++ b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
@@ -22,6 +22,7 @@
 public class ReadingListTable extends DatabaseTable<ReadingListRow> {
     private static final int DB_VER_INTRODUCED = 13;
     private static final int DB_VER_READING_LISTS_REORGANIZED = 17;
+    private static final int DB_VER_REMOTE_ID_INTRODUCED = 18;
 
     public ReadingListTable() {
         super(ReadingListContract.TABLE, ReadingListContract.List.URI);
@@ -35,6 +36,7 @@
                 .mtime(ReadingListContract.List.MTIME.val(cursor))
                 .atime(ReadingListContract.List.ATIME.val(cursor))
                 .description(ReadingListContract.List.DESCRIPTION.val(cursor))
+                .remoteId(ReadingListContract.List.REMOTE_ID.val(cursor))
                 .build();
     }
 
@@ -49,6 +51,8 @@
                 cols.add(ReadingListContract.List.ATIME);
                 cols.add(ReadingListContract.List.DESCRIPTION);
                 return cols.toArray(new Column<?>[cols.size()]);
+            case DB_VER_REMOTE_ID_INTRODUCED:
+                return new Column<?>[] { ReadingListContract.List.REMOTE_ID };
             default:
                 return super.getColumnsAdded(version);
         }
@@ -69,6 +73,7 @@
         contentValues.put(ReadingListContract.List.MTIME.getName(), 
row.mtime());
         contentValues.put(ReadingListContract.List.ATIME.getName(), 
row.atime());
         contentValues.put(ReadingListContract.List.DESCRIPTION.getName(), 
row.getDescription());
+        contentValues.put(ReadingListContract.List.REMOTE_ID.getName(), 
row.remoteId());
         return contentValues;
     }
 
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListClient.java 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListClient.java
new file mode 100644
index 0000000..694a21b
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListClient.java
@@ -0,0 +1,118 @@
+package org.wikipedia.readinglist.sync;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.concurrency.CallbackTask;
+import org.wikipedia.csrf.CsrfTokenClient;
+import org.wikipedia.dataclient.WikiSite;
+import org.wikipedia.dataclient.okhttp.HttpStatusException;
+import org.wikipedia.dataclient.okhttp.cache.SaveHeader;
+import org.wikipedia.dataclient.restbase.RbDefinition;
+import org.wikipedia.dataclient.retrofit.RbCachedService;
+import org.wikipedia.dataclient.retrofit.RetrofitFactory;
+import org.wikipedia.dataclient.retrofit.WikiCachedService;
+import org.wikipedia.readinglist.ReadingList;
+import org.wikipedia.settings.Prefs;
+import org.wikipedia.util.log.L;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.Header;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public class ReadingListClient {
+
+
+
+    // TODO!!!!!!!!!!!!!! Restore to uncommented version when ready.
+    //@NonNull private final WikiCachedService<Service> cachedService = new 
RbCachedService<>(Service.class);
+    @NonNull private final WikiCachedService<Service> cachedService = new 
RbCachedService<Service>(Service.class) {
+        @NonNull @Override protected Retrofit create() {
+            return 
RetrofitFactory.newInstance("https://readinglists.wmflabs.org/api/rest_v1/";,
+                    new WikiSite("https://readinglists.wmflabs.org";));
+        }
+    };
+
+
+
+    @NonNull private final WikiSite wiki;
+
+    public ReadingListClient(@NonNull WikiSite wiki) {
+        this.wiki = wiki;
+    }
+
+    public List<SyncedReadingLists.RemoteReadingList> 
getListsChangedSince(@NonNull String date) throws Throwable {
+        Response<SyncedReadingLists> response = 
cachedService.service(wiki).getListsChangedSince(date).execute();
+        SyncedReadingLists lists = response.body();
+        if (lists == null || lists.getLists() == null) {
+            throw new IOException("Incorrect response format.");
+        }
+        // TODO: check for "not-set-up" error
+        return lists.getLists();
+    }
+
+    private void setup(@NonNull String csrfToken) throws Throwable {
+        try {
+            cachedService.service(wiki).setup(csrfToken).execute();
+        } catch (Throwable t) {
+            if (isErrorType(t, "already-set-up")) {
+                return;
+            }
+            throw t;
+        }
+    }
+
+    private List<SyncedReadingLists.RemoteReadingList> getRemoteLists() throws 
Throwable {
+        Response<SyncedReadingLists> response = 
cachedService.service(wiki).getLists().execute();
+        SyncedReadingLists lists = response.body();
+        if (lists == null || lists.getLists() == null) {
+            throw new IOException("Incorrect response format.");
+        }
+        // TODO: check for "not-set-up" error
+        return lists.getLists();
+    }
+
+
+    public boolean isErrorType(Throwable t, @NonNull String errorType) {
+        return (t instanceof HttpStatusException
+                && ((HttpStatusException) t).serviceError() != null
+                && ((HttpStatusException) 
t).serviceError().getTitle().contains(errorType));
+    }
+
+    private interface Service {
+
+        @POST("data/lists/setup")
+        @FormUrlEncoded
+        @NonNull
+        Call<Void> setup(@Field("csrf_token") String token);
+
+        @POST("data/lists/teardown")
+        @FormUrlEncoded
+        @NonNull
+        Call<Void> tearDown(@Field("csrf_token") String token);
+
+        @GET("data/lists/")
+        @NonNull
+        Call<SyncedReadingLists> getLists();
+
+        @GET("data/lists/changes/since/{date}")
+        @NonNull
+        Call<SyncedReadingLists> getListsChangedSince(@Path("date") String 
iso8601Date);
+
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSyncService.java 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSyncService.java
new file mode 100644
index 0000000..7dbd47d
--- /dev/null
+++ 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSyncService.java
@@ -0,0 +1,29 @@
+package org.wikipedia.readinglist.sync;
+
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.Intent;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class ReadingListSyncService extends Service {
+    @NonNull private static final Object SYNC_ADAPTER_LOCK = new Object();
+    @Nullable private static AbstractThreadedSyncAdapter SYNC_ADAPTER;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        synchronized (SYNC_ADAPTER_LOCK) {
+            if (SYNC_ADAPTER == null) {
+                SYNC_ADAPTER = new 
ReadingListSyncAdapter(getApplicationContext(), true);
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return SYNC_ADAPTER == null ? null : 
SYNC_ADAPTER.getSyncAdapterBinder();
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
index d559bc8..76c95f4 100644
--- 
a/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
+++ 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
@@ -1,9 +1,12 @@
 package org.wikipedia.readinglist.sync;
 
+import android.content.ContentResolver;
+import android.os.Bundle;
 import android.os.Handler;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
+import org.wikipedia.BuildConfig;
 import org.wikipedia.WikipediaApp;
 import org.wikipedia.auth.AccountUtil;
 import org.wikipedia.concurrency.CallbackTask;
@@ -57,6 +60,14 @@
     }
 
     public void sync() {
+
+
+
+        ReadingListSyncAdapter.manualSync();
+
+
+
+
         if (!ReleaseUtil.isPreBetaRelease()  // TODO: remove when ready for 
beta/production
                 || !AccountUtil.isLoggedIn()
                 || !(isReadingListSyncEnabled() || 
isReadingListsRemoteDeletePending())) {
@@ -64,6 +75,7 @@
             L.d("Skipped sync of reading lists.");
             return;
         }
+        /*
         UserOptionDataClientSingleton.instance().get(new 
UserOptionDataClient.UserInfoCallback() {
             @Override
             public void success(@NonNull final UserInfo info) {
@@ -76,6 +88,7 @@
                 });
             }
         });
+        */
     }
 
     public void syncSavedPages() {
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/sync/SyncedReadingLists.java 
b/app/src/main/java/org/wikipedia/readinglist/sync/SyncedReadingLists.java
new file mode 100644
index 0000000..28c0c13
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/readinglist/sync/SyncedReadingLists.java
@@ -0,0 +1,65 @@
+package org.wikipedia.readinglist.sync;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.apache.commons.lang3.StringUtils;
+import org.wikipedia.json.annotations.Required;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SyncedReadingLists {
+
+    @SuppressWarnings("unused,NullableProblems") @Nullable private 
List<RemoteReadingList> lists;
+
+    @Nullable public List<RemoteReadingList> getLists() {
+        return lists;
+    }
+
+    public class RemoteReadingList {
+        @SuppressWarnings("unused") private int id;
+        @SuppressWarnings("unused,NullableProblems") @Required @NonNull 
private String name;
+        @SuppressWarnings("unused") @SerializedName("default") private boolean 
isDefault;
+        @SuppressWarnings("unused") @Nullable private String description;
+
+        @NonNull public String name() {
+            return name;
+        }
+
+        @NonNull public String description() {
+            return StringUtils.defaultString(description);
+        }
+
+        //@NonNull public List<RemoteReadingListPage> pages() {
+        //    return pages;
+        //}
+    }
+
+    /*
+    public static class RemoteReadingListPage {
+        @NonNull private String lang;
+        private int namespace;
+        @NonNull private String title;
+
+        RemoteReadingListPage(@NonNull String lang, int namespace, @NonNull 
String title) {
+            this.lang = lang;
+            this.namespace = namespace;
+            this.title = title;
+        }
+
+        @NonNull public String lang() {
+            return lang;
+        }
+
+        public int namespace() {
+            return namespace;
+        }
+
+        @NonNull public String title() {
+            return title;
+        }
+    }*/
+}
diff --git 
a/app/src/main/java/org/wikipedia/useroption/dataclient/UserOptionDataClient.java
 
b/app/src/main/java/org/wikipedia/useroption/dataclient/UserOptionDataClient.java
index f7f0dc7..f53ba25 100644
--- 
a/app/src/main/java/org/wikipedia/useroption/dataclient/UserOptionDataClient.java
+++ 
b/app/src/main/java/org/wikipedia/useroption/dataclient/UserOptionDataClient.java
@@ -42,7 +42,7 @@
     public void get(@NonNull final UserInfoCallback callback) {
         // Get a CSRF token, even though we won't use it, to ensure that the 
user is properly
         // logged in. Otherwise, we might receive user-options for an 
anonymous IP "user".
-        new CsrfTokenClient(wiki, app().getWikiSite()).request(new 
TokenCallback() {
+        new CsrfTokenClient(wiki, app().getWikiSite()).request(new 
CsrfTokenClient.DefaultCallback() {
             @Override
             public void success(@NonNull String token) {
                 service.get().enqueue(new Callback<MwQueryResponse>() {
@@ -64,7 +64,7 @@
     }
 
     public void post(@NonNull final UserOption option, @Nullable final 
UserOptionPostCallback callback) {
-        new CsrfTokenClient(wiki, app().getWikiSite()).request(new 
TokenCallback() {
+        new CsrfTokenClient(wiki, app().getWikiSite()).request(new 
CsrfTokenClient.DefaultCallback() {
             @Override
             public void success(@NonNull String token) {
                 service.post(token, option.key(), option.val()).enqueue(new 
Callback<PostResponse>() {
@@ -90,7 +90,7 @@
     }
 
     public void delete(@NonNull final String key, @Nullable final 
UserOptionPostCallback callback) {
-        new CsrfTokenClient(wiki, app().getWikiSite()).request(new 
TokenCallback() {
+        new CsrfTokenClient(wiki, app().getWikiSite()).request(new 
CsrfTokenClient.DefaultCallback() {
             @Override
             public void success(@NonNull String token) {
                 service.delete(token, key).enqueue(new 
Callback<PostResponse>() {
@@ -117,22 +117,6 @@
 
     private static WikipediaApp app() {
         return WikipediaApp.getInstance();
-    }
-
-    private class TokenCallback implements CsrfTokenClient.Callback {
-        @Override
-        public void success(@NonNull String token) {
-        }
-
-        @Override
-        public void failure(@NonNull Throwable caught) {
-            L.e(caught);
-        }
-
-        @Override
-        public void twoFactorPrompt() {
-            // TODO: warn the user that they need to re-login with 2FA.
-        }
     }
 
     // todo: rename service
diff --git 
a/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java 
b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
index 4d9516f..0eca60d 100644
--- a/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
+++ b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
@@ -37,12 +37,14 @@
             return;
         }
 
+        /*
         boolean uploadOnly = 
extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD);
 
         upload();
         if (!uploadOnly) {
             download();
         }
+        */
     }
 
     private synchronized void download() {

-- 
To view, visit https://gerrit.wikimedia.org/r/391048
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia82c4c5288c28ae83f7a2d56d603cae14e86adb2
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <dbr...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to