package org.wikipedia.useroption.dataclient; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.google.gson.annotations.SerializedName; import org.wikipedia.WikipediaApp; import org.wikipedia.csrf.CsrfTokenClient; import org.wikipedia.dataclient.ServiceError; import org.wikipedia.dataclient.WikiSite; import org.wikipedia.dataclient.mwapi.MwPostResponse; import org.wikipedia.dataclient.mwapi.MwQueryResponse; import org.wikipedia.dataclient.retrofit.RetrofitFactory; import org.wikipedia.useroption.UserOption; import org.wikipedia.util.log.L; import java.io.IOException; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; public class DefaultUserOptionDataClient implements UserOptionDataClient { @NonNull private final WikiSite wiki; @NonNull private final Service service; public DefaultUserOptionDataClient(@NonNull WikiSite wiki) { this.wiki = wiki; service = RetrofitFactory.newInstance(wiki).create(Service.class); } @NonNull @Override public UserInfo get() throws IOException { Response<MwQueryResponse<QueryUserInfo>> rsp = service.get().execute(); if (rsp.body().success()) { //noinspection ConstantConditions return rsp.body().query().userInfo(); } ServiceError err = rsp.body() == null || rsp.body().getError() == null ? null : rsp.body().getError(); throw new IOException(err == null ? rsp.message() : err.getDetails()); } @Override public void post(@NonNull final UserOption option) throws IOException { new CsrfTokenClient(wiki, app().getWikiSite()).request(new TokenCallback() { @Override public void success(@NonNull String token) { service.post(token, option.key(), option.val()).enqueue(new Callback<PostResponse>() { @Override public void onResponse(Call<PostResponse> call, Response<PostResponse> response) { if (response.body() != null && !response.body().success(response.body().result())) { L.e("Bad response for wiki " + wiki.host() + " = " + response.body().result()); } notifyThis(); } @Override public void onFailure(Call<PostResponse> call, Throwable caught) { L.e(caught); notifyThis(); } }); } }); waitForThis(); } @Override public void delete(@NonNull final String key) throws IOException { new CsrfTokenClient(wiki, app().getWikiSite()).request(new TokenCallback() { @Override public void success(@NonNull String token) { service.delete(token, key).enqueue(new Callback<PostResponse>() { @Override public void onResponse(Call<PostResponse> call, Response<PostResponse> response) { if (response.body() != null && !response.body().success(response.body().result())) { L.e("Bad response for wiki " + wiki.host() + " = " + response.body().result()); } notifyThis(); } @Override public void onFailure(Call<PostResponse> call, Throwable caught) { L.e(caught); notifyThis(); } }); } }); waitForThis(); } private synchronized void waitForThis() { try { wait(); } catch (InterruptedException e) { L.d(e); } } private synchronized void notifyThis() { notify(); } 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 private interface Service { String ACTION = "w/api.php?format=json&formatversion=2&action="; @GET(ACTION + "query&meta=userinfo&uiprop=options") @NonNull Call<MwQueryResponse<QueryUserInfo>> get(); @FormUrlEncoded @POST(ACTION + "options") @NonNull Call<PostResponse> post(@Field("token") @NonNull String token, @Query("optionname") @NonNull String key, @Query("optionvalue") @Nullable String value); @FormUrlEncoded @POST(ACTION + "options") @NonNull Call<PostResponse> delete(@Field("token") @NonNull String token, @Query("change") @NonNull String key); } private static class PostResponse extends MwPostResponse { @SuppressWarnings("unused") private String options; public String result() { return options; } } private static class QueryUserInfo { @SuppressWarnings("unused") @SerializedName("userinfo") private UserInfo userInfo; UserInfo userInfo() { return userInfo; } } }