From f7c57c03ba159b78da5093d5b9b9ec1c10c648ad Mon Sep 17 00:00:00 2001 From: Vivek Maskara Date: Mon, 4 Jun 2018 13:20:50 +0530 Subject: [PATCH] Log P18 edits with custom tag (#1583) * Log P18 edits by adding custom tag to the edits * Add javadocs --- .../mwapi/ApacheHttpClientMediaWikiApi.java | 39 +++++- .../free/nrw/commons/mwapi/MediaWikiApi.java | 5 +- .../nrw/commons/upload/UploadService.java | 49 +------ .../commons/wikidata/WikidataEditService.java | 130 ++++++++++++++++++ 4 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java index 3eeeec4e2..96bf9cbcf 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java @@ -394,12 +394,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { * @param property the property to be edited, for eg P18 for images * @param snaktype the type of value stored for that property * @param value the actual value to be stored for the property, for eg filename in case of P18 - * @return returns true if the claim is successfully created + * @return returns revisionId if the claim is successfully created else returns null * @throws IOException */ @Nullable @Override - public boolean wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException { + public String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException { + Timber.d("Filename is %s", value); ApiResult result = wikidataApi.action("wbcreateclaim") .param("entity", entityId) .param("centralauthtoken", getCentralAuthToken()) @@ -410,13 +411,45 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { .post(); if (result == null || result.getNode("api") == null) { - return false; + return null; } Node node = result.getNode("api").getDocument(); Element element = (Element) node; if (element != null && element.getAttribute("success").equals("1")) { + return result.getString("api/pageinfo/@lastrevid"); + } else { + Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info")); + } + return null; + } + + /** + * Adds the wikimedia-commons-app tag to the edits made on wikidata + * @param revisionId + * @return + * @throws IOException + */ + @Nullable + @Override + public boolean addWikidataEditTag(String revisionId) throws IOException { + ApiResult result = wikidataApi.action("tag") + .param("revid", revisionId) + .param("centralauthtoken", getCentralAuthToken()) + .param("token", getWikidataCsrfToken()) + .param("add", "wikimedia-commons-app") + .param("reason", "Add tag for edits made using Android Commons app") + .post(); + + if (result == null || result.getNode("api") == null) { + return false; + } + + Node node = result.getNode("api").getDocument(); + Element element = (Element) node; + + if (element != null && element.getAttribute("status").equals("success")) { return true; } else { Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info")); diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java index 869205f79..b4398319f 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java @@ -54,7 +54,10 @@ public interface MediaWikiApi { String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException; @Nullable - boolean wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException; + String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException; + + @Nullable + boolean addWikidataEditTag(String revisionId) throws IOException; @NonNull MediaResult fetchMediaByFilename(String filename) throws IOException; diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index 39d53d2f0..4c05eb7b0 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -39,6 +39,7 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.UploadResult; import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.wikidata.WikidataEditListener; +import fr.free.nrw.commons.wikidata.WikidataEditService; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -55,11 +56,9 @@ public class UploadService extends HandlerService { public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign"; @Inject MediaWikiApi mwApi; - @Inject WikidataEditListener wikidataEditListener; + @Inject WikidataEditService wikidataEditService; @Inject SessionManager sessionManager; - @Inject @Named("default_preferences") SharedPreferences prefs; @Inject ContributionDao contributionDao; - @Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs; private NotificationManager notificationManager; private NotificationCompat.Builder curProgressNotification; @@ -262,7 +261,7 @@ public class UploadService extends HandlerService { if (!resultStatus.equals("Success")) { showFailedNotification(contribution); } else { - editWikidataProperty(contribution); + wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), filename); contribution.setFilename(uploadResult.getCanonicalFilename()); contribution.setImageUrl(uploadResult.getImageUrl()); contribution.setState(Contribution.STATE_COMPLETED); @@ -285,48 +284,6 @@ public class UploadService extends HandlerService { } } - /** - * Edits the wikidata entity by adding the P18 property to it. - * Adding the P18 edit requires calling the wikidata API to create a claim against the entity - * @param contribution - */ - @SuppressLint("CheckResult") - private void editWikidataProperty(Contribution contribution) { - Timber.d("Upload successful with wiki data entity id as %s", contribution.getWikiDataEntityId()); - Timber.d("Attempting to edit Wikidata property %s", contribution.getWikiDataEntityId()); - Observable.fromCallable(() -> mwApi.wikidatCreateClaim(contribution.getWikiDataEntityId(), "P18", "value", getFileName(contribution))) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> handleClaimResult(contribution, result), throwable -> { - Timber.e(throwable, "Error occurred while making claim"); - ViewUtil.showLongToast(getBaseContext(), getResources().getString(R.string.wikidata_edit_failure)); - }); - } - - private void handleClaimResult(Contribution contribution, Boolean result) { - if(result) { - wikidataEditListener.onSuccessfulWikidataEdit(); - String title = directPrefs.getString("Title", ""); - String successStringTemplate = getResources().getString(R.string.successful_wikidata_edit); - String successMessage = String.format(Locale.getDefault(), successStringTemplate, title); - ViewUtil.showLongToast(getBaseContext(), successMessage); - } else { - Timber.d("Unable to make wiki data edit for entity %s", contribution.getWikiDataEntityId()); - ViewUtil.showLongToast(getBaseContext(), getResources().getString(R.string.wikidata_edit_failure)); - } - } - - /** - * Formats and returns the filename as accepted by the wiki base API - * https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim - * @param contribution - * @return - */ - private String getFileName(Contribution contribution) { - String filename = String.format("\"%s\"", contribution.getFilename().replace("File:", "")); - return filename; - } - @SuppressLint("StringFormatInvalid") private void showFailedNotification(Contribution contribution) { Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true) diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java new file mode 100644 index 000000000..b43ed329c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java @@ -0,0 +1,130 @@ +package fr.free.nrw.commons.wikidata; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; + +import java.util.Locale; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.mwapi.MediaWikiApi; +import fr.free.nrw.commons.utils.ViewUtil; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +/** + * This class is meant to handle the Wikidata edits made through the app + * It will talk with MediaWikiApi to make necessary API calls, log the edits and fire listeners + * on successful edits + */ +@Singleton +public class WikidataEditService { + + private final Context context; + private final MediaWikiApi mediaWikiApi; + private final WikidataEditListener wikidataEditListener; + private final SharedPreferences directPrefs; + + @Inject + public WikidataEditService(Context context, + MediaWikiApi mediaWikiApi, + WikidataEditListener wikidataEditListener, + @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs) { + this.context = context; + this.mediaWikiApi = mediaWikiApi; + this.wikidataEditListener = wikidataEditListener; + this.directPrefs = directPrefs; + } + + /** + * Create a P18 claim and log the edit with custom tag + * @param wikidataEntityId + * @param fileName + */ + public void createClaimWithLogging(String wikidataEntityId, String fileName) { + editWikidataProperty(wikidataEntityId, fileName); + } + + /** + * Edits the wikidata entity by adding the P18 property to it. + * Adding the P18 edit requires calling the wikidata API to create a claim against the entity + * + * @param wikidataEntityId + * @param fileName + */ + @SuppressLint("CheckResult") + private void editWikidataProperty(String wikidataEntityId, String fileName) { + Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId); + Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId); + Observable.fromCallable(() -> { + String propertyValue = getFileName(fileName); + return mediaWikiApi.wikidatCreateClaim(wikidataEntityId, "P18", "value", propertyValue); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(revisionId -> handleClaimResult(wikidataEntityId, revisionId), throwable -> { + Timber.e(throwable, "Error occurred while making claim"); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + }); + } + + private void handleClaimResult(String wikidataEntityId, String revisionId) { + if (revisionId != null) { + wikidataEditListener.onSuccessfulWikidataEdit(); + showSuccessToast(); + logEdit(revisionId); + } else { + Timber.d("Unable to make wiki data edit for entity %s", wikidataEntityId); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + } + } + + /** + * Log the Wikidata edit by adding Wikimedia Commons App tag to the edit + * @param revisionId + */ + @SuppressLint("CheckResult") + private void logEdit(String revisionId) { + Observable.fromCallable(() -> mediaWikiApi.addWikidataEditTag(revisionId)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result) { + Timber.d("Wikidata edit was tagged successfully"); + } else { + Timber.d("Wikidata edit couldn't be tagged"); + } + }, throwable -> { + Timber.e(throwable, "Error occurred while adding tag to the edit"); + }); + } + + /** + * Show a success toast when the edit is made successfully + */ + private void showSuccessToast() { + String title = directPrefs.getString("Title", ""); + String successStringTemplate = context.getString(R.string.successful_wikidata_edit); + String successMessage = String.format(Locale.getDefault(), successStringTemplate, title); + ViewUtil.showLongToast(context, successMessage); + } + + /** + * Formats and returns the filename as accepted by the wiki base API + * https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim + * + * @param fileName + * @return + */ + private String getFileName(String fileName) { + fileName = String.format("\"%s\"", fileName.replace("File:", "")); + Timber.d("Wikidata property name is %s", fileName); + return fileName; + } +}