With wikidata changes (#3199)

This commit is contained in:
Vivek Maskara 2019-11-23 15:26:18 +05:30 committed by Ashish Kumar
parent 02f5cdac59
commit 891d9ec41c
15 changed files with 360 additions and 84 deletions

View file

@ -222,7 +222,7 @@ android {
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.beta.wmflabs.org\""
buildConfigField "String", "WIKIDATA_URL", "\"https://wikidata.org\""
buildConfigField "String", "WIKIDATA_URL", "\"https:/www./wikidata.org\""
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""

View file

@ -1,12 +1,5 @@
package fr.free.nrw.commons;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_COMMENT;
import android.annotation.SuppressLint;
import android.app.Application;
import android.app.NotificationChannel;
@ -16,12 +9,28 @@ import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
import org.acra.annotation.AcraMailSender;
import org.acra.data.StringFormat;
import org.wikipedia.AppAdapter;
import org.wikipedia.language.AppLanguageLookUpTable;
import java.io.File;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
@ -40,18 +49,15 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.internal.functions.Functions;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
import javax.inject.Inject;
import javax.inject.Named;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
import org.acra.annotation.AcraMailSender;
import org.acra.data.StringFormat;
import org.wikipedia.AppAdapter;
import org.wikipedia.language.AppLanguageLookUpTable;
import timber.log.Timber;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_COMMENT;
@AcraCore(
buildConfigClass = BuildConfig.class,
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
@ -114,6 +120,7 @@ public class CommonsApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
ACRA.init(this);

View file

@ -1,12 +1,13 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
import org.wikipedia.dataclient.okhttp.HttpStatusException;
import java.io.File;
import java.io.IOException;
import androidx.annotation.NonNull;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;

View file

@ -29,7 +29,6 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.wikidata.WikidataClient;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@ -58,8 +57,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
@Inject @Named("default_preferences") JsonKvStore kvStore;
@Inject ContributionController controller;
@Inject
WikidataClient wikidataClient;
private Animation fab_close;
private Animation fab_open;

View file

@ -31,6 +31,7 @@ import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.mwapi.UserInterface;
import fr.free.nrw.commons.review.ReviewInterface;
import fr.free.nrw.commons.upload.UploadInterface;
import fr.free.nrw.commons.wikidata.WikidataInterface;
import okhttp3.Cache;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
@ -51,7 +52,6 @@ public class NetworkingModule {
private static final String NAMED_WIKI_DATA_WIKI_SITE = "wikidata-wikisite";
public static final String NAMED_COMMONS_CSRF = "commons-csrf";
public static final String NAMED_WIKI_DATA_CSRF = "wikidata-csrf";
@Provides
@Singleton
@ -97,14 +97,6 @@ public class NetworkingModule {
return new CsrfTokenClient(commonsWikiSite, commonsWikiSite);
}
@Named(NAMED_WIKI_DATA_CSRF)
@Provides
@Singleton
public CsrfTokenClient provideWikidataCsrfTokenClient(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite,
@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikidataWikiSite) {
return new CsrfTokenClient(wikidataWikiSite, commonsWikiSite);
}
@Provides
@Singleton
public LoginClient provideLoginClient() {
@ -200,15 +192,6 @@ public class NetworkingModule {
return new PageEditClient(csrfTokenClient, pageEditInterface, service);
}
@Named("wikidata-page-edit")
@Provides
@Singleton
public PageEditClient provideWikidataPageEditClient(@Named(NAMED_WIKI_DATA_CSRF) CsrfTokenClient csrfTokenClient,
@Named("wikidata-page-edit-service") PageEditInterface pageEditInterface,
@Named("wikidata-service") Service service) {
return new PageEditClient(csrfTokenClient, pageEditInterface, service);
}
@Provides
@Singleton
public MediaInterface provideMediaInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
@ -226,4 +209,10 @@ public class NetworkingModule {
public UserInterface provideUserInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, UserInterface.class);
}
@Provides
@Singleton
public WikidataInterface provideWikidataInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikiDataWikiSite) {
return ServiceFactory.get(wikiDataWikiSite, BuildConfig.WIKIDATA_URL, WikidataInterface.class);
}
}

View file

@ -3,7 +3,20 @@ package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
@ -18,15 +31,6 @@ import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subjects.BehaviorSubject;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import timber.log.Timber;
@Singleton

View file

@ -87,7 +87,6 @@ public class UploadService extends HandlerService<Contribution> {
}
public void onProgress(long transferred, long total) {
Timber.d("Uploaded %d of %d", transferred, total);
if (!notificationTitleChanged) {
curNotification.setContentTitle(notificationProgressTitle);
notificationTitleChanged = true;

View file

@ -1,40 +1,70 @@
package fr.free.nrw.commons.wikidata;
import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.Service;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.wikidata.model.AddEditTagResponse;
import io.reactivex.Observable;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_WIKI_DATA_CSRF;
import io.reactivex.ObservableSource;
import okhttp3.MediaType;
import okhttp3.RequestBody;
@Singleton
public class WikidataClient {
private final Service service;
private final CsrfTokenClient csrfTokenClient;
private final WikidataInterface wikidataInterface;
@Inject
public WikidataClient(@Named("wikidata-service") Service service,
@Named(NAMED_WIKI_DATA_CSRF) CsrfTokenClient csrfTokenClient) {
this.service = service;
this.csrfTokenClient = csrfTokenClient;
public WikidataClient(WikidataInterface wikidataInterface) {
this.wikidataInterface = wikidataInterface;
}
public Observable<Long> createClaim(String entityId, String property, String snaktype, String value) {
try {
return service.postCreateClaim(entityId, snaktype, property, value, "en", csrfTokenClient.getTokenBlocking())
.map(mwPostResponse -> {
if (mwPostResponse.getSuccessVal() == 1) {
return 1L;
}
return -1L;
});
} catch (Throwable throwable) {
return Observable.just(-1L);
}
/**
* Create wikidata claim to add P18 value
* @param entityId wikidata entity ID
* @param value value of the P18 edit
* @return revisionID of the edit
*/
Observable<Long> createClaim(String entityId, String value) {
return getCsrfToken()
.flatMap(csrfToken -> wikidataInterface.postCreateClaim(toRequestBody(entityId),
toRequestBody("value"),
toRequestBody("P18"),
toRequestBody(value),
toRequestBody("en"),
toRequestBody(csrfToken)))
.map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
}
/**
* Converts string value to RequestBody for multipart request
*/
private RequestBody toRequestBody(String value) {
return RequestBody.create(MediaType.parse("text/plain"), value);
}
/**
* Get csrf token for wikidata edit
*/
@NotNull
private Observable<String> getCsrfToken() {
return wikidataInterface.getCsrfToken().map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
}
/**
* Add edit tag for a given revision ID. The app currently uses this to tag P18 edits
* @param revisionId revision ID of the page edited
* @param tag to be added
* @param reason to be mentioned
*/
ObservableSource<AddEditTagResponse> addEditTag(Long revisionId, String tag, String reason) {
return getCsrfToken()
.flatMap(csrfToken -> wikidataInterface.addEditTag(String.valueOf(revisionId),
tag,
reason,
csrfToken));
}
}

View file

@ -10,7 +10,6 @@ import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -32,19 +31,16 @@ public class WikidataEditService {
private final WikidataEditListener wikidataEditListener;
private final JsonKvStore directKvStore;
private final WikidataClient wikidataClient;
private final PageEditClient wikiDataPageEditClient;
@Inject
public WikidataEditService(Context context,
WikidataEditListener wikidataEditListener,
@Named("default_preferences") JsonKvStore directKvStore,
WikidataClient wikidataClient,
@Named("wikidata-page-edit") PageEditClient wikiDataPageEditClient) {
WikidataEditService(Context context,
WikidataEditListener wikidataEditListener,
@Named("default_preferences") JsonKvStore directKvStore,
WikidataClient wikidataClient) {
this.context = context;
this.wikidataEditListener = wikidataEditListener;
this.directKvStore = directKvStore;
this.wikidataClient = wikidataClient;
this.wikiDataPageEditClient = wikiDataPageEditClient;
}
/**
@ -85,11 +81,11 @@ public class WikidataEditService {
String propertyValue = getFileName(fileName);
Timber.d(propertyValue);
wikidataClient.createClaim(wikidataEntityId, "P18", "value", propertyValue)
Timber.d("Entity id is %s and property value is %s", wikidataEntityId, propertyValue);
wikidataClient.createClaim(wikidataEntityId, propertyValue)
.flatMap(revisionId -> {
if (revisionId != -1) {
return wikiDataPageEditClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON);
return wikidataClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON);
}
throw new RuntimeException("Unable to edit wikidata item");
})

View file

@ -0,0 +1,54 @@
package fr.free.nrw.commons.wikidata;
import androidx.annotation.NonNull;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import fr.free.nrw.commons.wikidata.model.AddEditTagResponse;
import fr.free.nrw.commons.wikidata.model.WbCreateClaimResponse;
import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
public interface WikidataInterface {
/**
* Wikidata create claim API. Posts a new claim for the given entity ID
*/
@Headers("Cache-Control: no-cache")
@POST("w/api.php?format=json&errorformat=plaintext&action=wbcreateclaim&errorlang=uselang")
@Multipart
Observable<WbCreateClaimResponse> postCreateClaim(@NonNull @Part("entity") RequestBody entity,
@NonNull @Part("snaktype") RequestBody snakType,
@NonNull @Part("property") RequestBody property,
@NonNull @Part("value") RequestBody value,
@NonNull @Part("uselang") RequestBody useLang,
@NonNull @Part("token") RequestBody token);
/**
* Add edit tag and reason for any revision
*/
@Headers("Cache-Control: no-cache")
@POST(MW_API_PREFIX + "action=tag")
@FormUrlEncoded
Observable<AddEditTagResponse> addEditTag(@NonNull @Field("revid") String revId,
@NonNull @Field("add") String tagName,
@NonNull @Field("reason") String reason,
@NonNull @Field("token") String token);
/**
* Get edit token for wikidata wiki site
*/
@Headers("Cache-Control: no-cache")
@GET(MW_API_PREFIX + "action=query&meta=tokens&type=csrf")
@NonNull
Observable<MwQueryResponse> getCsrfToken();
}

View file

@ -0,0 +1,25 @@
package fr.free.nrw.commons.wikidata.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Response class for add edit tag
*/
public class AddEditTagResponse {
@SerializedName("tag")
@Expose
private List<EditTag> tag = null;
public List<EditTag> getTag() {
return tag;
}
public void setTag(List<EditTag> tag) {
this.tag = tag;
}
}

View file

@ -0,0 +1,56 @@
package fr.free.nrw.commons.wikidata.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Tag class used when adding wikidata edit tag
*/
public class EditTag {
@SerializedName("revid")
@Expose
private Integer revid;
@SerializedName("status")
@Expose
private String status;
@SerializedName("actionlogid")
@Expose
private Integer actionlogid;
@SerializedName("added")
@Expose
private List<String> added;
@SerializedName("removed")
@Expose
private List<Object> removed;
public EditTag(Integer revid, String status, Integer actionlogid, List<String> added, List<Object> removed) {
this.revid = revid;
this.status = status;
this.actionlogid = actionlogid;
this.added = added;
this.removed = removed;
}
public Integer getRevid() {
return revid;
}
public String getStatus() {
return status;
}
public Integer getActionlogid() {
return actionlogid;
}
public List<String> getAdded() {
return added;
}
public List<Object> getRemoved() {
return removed;
}
}

View file

@ -0,0 +1,21 @@
package fr.free.nrw.commons.wikidata.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* PageInfo model class with last revision id of the edited Wikidata entity
*/
public class PageInfo {
@SerializedName("lastrevid")
@Expose
private Long lastrevid;
public PageInfo(Long lastrevid) {
this.lastrevid = lastrevid;
}
public Long getLastrevid() {
return lastrevid;
}
}

View file

@ -0,0 +1,30 @@
package fr.free.nrw.commons.wikidata.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* Wikidata create claim response model class
*/
public class WbCreateClaimResponse {
@SerializedName("pageinfo")
@Expose
private PageInfo pageinfo;
@SerializedName("success")
@Expose
private Integer success;
public WbCreateClaimResponse(PageInfo pageinfo, Integer success) {
this.pageinfo = pageinfo;
this.success = success;
}
public PageInfo getPageinfo() {
return pageinfo;
}
public Integer getSuccess() {
return success;
}
}

View file

@ -0,0 +1,67 @@
package fr.free.nrw.commons.wikidata
import android.content.Context
import com.nhaarman.mockito_kotlin.verifyZeroInteractions
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.wikidata.model.AddEditTagResponse
import io.reactivex.Observable
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.MockitoAnnotations
class WikidataEditServiceTest {
@Mock
internal var context: Context? = null
@Mock
internal var wikidataEditListener: WikidataEditListener? = null
@Mock
internal var directKvStore: JsonKvStore? = null
@Mock
internal var wikidataClient: WikidataClient? = null
@InjectMocks
var wikidataEditService: WikidataEditService? = null
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
fun noClaimsWhenEntityIdIsNull() {
wikidataEditService!!.createClaimWithLogging(null, "Test.jpg")
verifyZeroInteractions(wikidataClient!!)
}
@Test
fun noClaimsWhenFileNameIsNull() {
wikidataEditService!!.createClaimWithLogging("Q1", null)
verifyZeroInteractions(wikidataClient!!)
}
@Test
fun noClaimsWhenLocationIsNotCorrect() {
`when`(directKvStore!!.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(false)
wikidataEditService!!.createClaimWithLogging("Q1", "Test.jpg")
verifyZeroInteractions(wikidataClient!!)
}
@Test
fun createClaimWithLogging() {
`when`(directKvStore!!.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(true)
`when`(wikidataClient!!.createClaim(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(1L))
`when`(wikidataClient!!.addEditTag(anyLong(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(mock(AddEditTagResponse::class.java)))
wikidataEditService!!.createClaimWithLogging("Q1", "Test.jpg")
verify(wikidataClient!!, times(1))
.createClaim(ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
}
}