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