From 73311970c59ca3cfa31fa35e11180788b85f2cd4 Mon Sep 17 00:00:00 2001 From: Paul Hawke Date: Mon, 9 Dec 2024 21:18:42 -0600 Subject: [PATCH] Convert wikidata/mwapi to kotlin (part 4) (#6010) * Convert ImageDetails to kotlin * Convert MwException/MwServiceError to kotlin * Convert ListUserResponse to kotlin * Convert MwPostResponse to kotlin * Convert MwQueryResponse to kotlin * Convert MwQueryResult to kotlin * Convert MwQueryPage to kotlin --------- Co-authored-by: Nicolas Raoul --- .../nrw/commons/auth/login/LoginClient.kt | 2 +- .../nrw/commons/media/MediaDetailFragment.kt | 2 +- .../free/nrw/commons/review/ReviewActivity.kt | 2 +- .../nrw/commons/review/ReviewController.kt | 4 +- .../free/nrw/commons/review/ReviewHelper.kt | 2 +- .../nrw/commons/review/ReviewImageFragment.kt | 5 +- .../free/nrw/commons/upload/UploadClient.kt | 2 +- .../commons/wikidata/mwapi/ImageDetails.java | 17 -- .../commons/wikidata/mwapi/ImageDetails.kt | 6 + .../wikidata/mwapi/ListUserResponse.java | 26 -- .../wikidata/mwapi/ListUserResponse.kt | 12 + .../commons/wikidata/mwapi/MwException.java | 44 ---- .../nrw/commons/wikidata/mwapi/MwException.kt | 15 ++ .../wikidata/mwapi/MwPostResponse.java | 16 -- .../commons/wikidata/mwapi/MwPostResponse.kt | 9 + .../commons/wikidata/mwapi/MwQueryPage.java | 229 ------------------ .../nrw/commons/wikidata/mwapi/MwQueryPage.kt | 186 ++++++++++++++ .../wikidata/mwapi/MwQueryResponse.java | 26 -- .../commons/wikidata/mwapi/MwQueryResponse.kt | 15 ++ .../commons/wikidata/mwapi/MwQueryResult.java | 187 -------------- .../commons/wikidata/mwapi/MwQueryResult.kt | 134 ++++++++++ .../commons/wikidata/mwapi/MwResponse.java | 23 -- .../nrw/commons/wikidata/mwapi/MwResponse.kt | 18 ++ .../wikidata/mwapi/MwServiceError.java | 29 --- .../commons/wikidata/mwapi/MwServiceError.kt | 18 ++ .../commons/review/ReviewControllerTest.kt | 2 +- .../nrw/commons/review/ReviewHelperTest.kt | 10 +- .../commons/wikidata/mwapi/MwQueryPageTest.kt | 48 ++++ 28 files changed, 474 insertions(+), 615 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.kt create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/wikidata/mwapi/MwQueryPageTest.kt diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt index 48ca37cf0e..2a799c8479 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt @@ -237,7 +237,7 @@ class LoginClient( .subscribe({ response: MwQueryResponse? -> loginResult.userId = response?.query()?.userInfo()?.id() ?: 0 loginResult.groups = - response?.query()?.getUserResponse(userName)?.groups ?: emptySet() + response?.query()?.getUserResponse(userName)?.getGroups() ?: emptySet() cb.success(loginResult) }, { caught: Throwable -> Timber.e(caught, "Login succeeded but getting group information failed. ") diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index 03eeac61e8..32785fd425 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -938,7 +938,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C Observable.defer { thanksClient.thank( - firstRevision.revisionId + firstRevision.revisionId() ) } .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt index cd2cbc8ad3..40eb24ed02 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt @@ -201,7 +201,7 @@ class ReviewActivity : BaseActivity() { val caption = getString( R.string.review_is_uploaded_by, fileName, - revision.user + revision.user() ) binding.tvImageCaption.text = caption binding.pbReviewImage.visibility = View.GONE diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt index 62652bd5bb..2450dd8502 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt @@ -12,7 +12,6 @@ import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage import java.util.ArrayList -import java.util.concurrent.Callable import javax.inject.Inject import javax.inject.Named @@ -27,7 +26,6 @@ import fr.free.nrw.commons.delete.DeleteHelper import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.utils.ViewUtil import io.reactivex.Observable -import io.reactivex.ObservableSource import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import timber.log.Timber @@ -175,7 +173,7 @@ class ReviewController @Inject constructor( if (firstRevision == null) return Observable.defer { - thanksClient.thank(firstRevision!!.revisionId) + thanksClient.thank(firstRevision!!.revisionId()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt index 17296a5c85..3ad15d8bfc 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt @@ -34,7 +34,7 @@ class ReviewHelper reviewInterface .getRecentChanges() .map { it.query()?.pages() } - .map(MutableList::shuffled) + .map { it.shuffled() } .flatMapIterable { changes: List? -> changes } .filter { isChangeReviewable(it) } diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt index 691c61f569..6c922c99cb 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt @@ -3,18 +3,15 @@ package fr.free.nrw.commons.review import android.graphics.Color import android.os.Bundle import android.text.Html -import android.text.TextUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import fr.free.nrw.commons.CommonsApplication -import fr.free.nrw.commons.Media import fr.free.nrw.commons.R import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException import fr.free.nrw.commons.databinding.FragmentReviewImageBinding import fr.free.nrw.commons.di.CommonsDaggerSupportFragment -import java.util.ArrayList import javax.inject.Inject @@ -126,7 +123,7 @@ class ReviewImageFragment : CommonsDaggerSupportFragment() { enableButtons() question = getString(R.string.review_thanks) - user = reviewActivity.reviewController.firstRevision?.user + user = reviewActivity.reviewController.firstRevision?.user() ?: savedInstanceState?.getString(SAVED_USER) //if the user is null because of whatsoever reason, review will not be sent anyways diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.kt index 54f9b112fd..8464c670f7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.kt @@ -270,7 +270,7 @@ class UploadClient if (uploadResult.upload == null) { val exception = gson.fromJson(uploadResponse, MwException::class.java) Timber.e(exception, "Error in uploading file from stash") - throw Exception(exception.getErrorCode()) + throw Exception(exception.errorCode) } uploadResult.upload } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.java deleted file mode 100644 index 3a4ac97026..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.java +++ /dev/null @@ -1,17 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.NonNull; - -public class ImageDetails { - - private String name; - private String title; - - @NonNull public String getName() { - return name; - } - - @NonNull public String getTitle() { - return title; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.kt new file mode 100644 index 0000000000..d6e2f57ddc --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ImageDetails.kt @@ -0,0 +1,6 @@ +package fr.free.nrw.commons.wikidata.mwapi + +class ImageDetails { + val name: String? = null + val title: String? = null +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.java deleted file mode 100644 index a71e910c30..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.collection.ArraySet; - -import com.google.gson.annotations.SerializedName; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - - -public class ListUserResponse { - @SerializedName("name") @Nullable private String name; - private long userid; - @Nullable private List groups; - - @Nullable public String name() { - return name; - } - - @NonNull public Set getGroups() { - return groups != null ? new ArraySet<>(groups) : Collections.emptySet(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.kt new file mode 100644 index 0000000000..f5a04fc8e6 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/ListUserResponse.kt @@ -0,0 +1,12 @@ +package fr.free.nrw.commons.wikidata.mwapi + +class ListUserResponse { + private val name: String? = null + private val userid: Long = 0 + private val groups: List? = null + + fun name(): String? = name + + fun getGroups(): Set = + groups?.toSet() ?: emptySet() +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.java deleted file mode 100644 index f526815767..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.java +++ /dev/null @@ -1,44 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.Nullable; -import java.util.List; - -public class MwException extends RuntimeException { - @Nullable private final MwServiceError error; - - @Nullable private final List errors; - - public MwException(@Nullable MwServiceError error, - @Nullable final List errors) { - this.error = error; - this.errors = errors; - } - - public String getErrorCode() { - if(error!=null) { - return error.getCode(); - } - return errors != null ? errors.get(0).getCode() : null; - } - - @Nullable public MwServiceError getError() { - return error; - } - - @Nullable - public String getTitle() { - if (error != null) { - return error.getTitle(); - } - return errors != null ? errors.get(0).getTitle() : null; - } - - @Override - @Nullable - public String getMessage() { - if (error != null) { - return error.getDetails(); - } - return errors != null ? errors.get(0).getDetails() : null; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.kt new file mode 100644 index 0000000000..9b003ecfa4 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwException.kt @@ -0,0 +1,15 @@ +package fr.free.nrw.commons.wikidata.mwapi + +class MwException( + val error: MwServiceError?, + private val errors: List? +) : RuntimeException() { + val errorCode: String? + get() = error?.code ?: errors?.get(0)?.code + + val title: String? + get() = error?.title ?: errors?.get(0)?.title + + override val message: String? + get() = error?.details ?: errors?.get(0)?.details +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.java deleted file mode 100644 index 294fdad0a7..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.Nullable; - -public class MwPostResponse extends MwResponse { - private int success; - - public boolean success(@Nullable String result) { - return "success".equals(result); - } - - public int getSuccessVal() { - return success; - } -} - diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.kt new file mode 100644 index 0000000000..a4ed55b7b2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwPostResponse.kt @@ -0,0 +1,9 @@ +package fr.free.nrw.commons.wikidata.mwapi + +open class MwPostResponse : MwResponse() { + val successVal: Int = 0 + + fun success(result: String?): Boolean = + "success" == result +} + diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.java deleted file mode 100644 index be886b2059..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.java +++ /dev/null @@ -1,229 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -import org.apache.commons.lang3.StringUtils; -import fr.free.nrw.commons.wikidata.model.gallery.ImageInfo; -import fr.free.nrw.commons.wikidata.model.BaseModel; - -import java.util.Collections; -import java.util.List; - -/** - * A class representing a standard page object as returned by the MediaWiki API. - */ -public class MwQueryPage extends BaseModel { - private int pageid; - private int index; - @NonNull private String title; - @NonNull private CategoryInfo categoryinfo; - @Nullable private List revisions; - @SerializedName("fileusage") @Nullable private List fileUsages; - @SerializedName("globalusage") @Nullable private List globalUsages; - @Nullable private List coordinates; - @Nullable private List categories; - @Nullable private Thumbnail thumbnail; - @Nullable private String description; - @SerializedName("imageinfo") @Nullable private List imageInfo; - @Nullable private String redirectFrom; - @Nullable private String convertedFrom; - @Nullable private String convertedTo; - - @NonNull public String title() { - return title; - } - - @NonNull public CategoryInfo categoryInfo() { - return categoryinfo; - } - - public int index() { - return index; - } - - @Nullable public List revisions() { - return revisions; - } - - @Nullable public List categories() { - return categories; - } - - @Nullable public List coordinates() { - // TODO: Handle null values in lists during deserialization, perhaps with a new - // @RequiredElements annotation and corresponding TypeAdapter - if (coordinates != null) { - coordinates.removeAll(Collections.singleton(null)); - } - return coordinates; - } - - public int pageId() { - return pageid; - } - - @Nullable public String thumbUrl() { - return thumbnail != null ? thumbnail.source() : null; - } - - @Nullable public String description() { - return description; - } - - @Nullable public ImageInfo imageInfo() { - return imageInfo != null ? imageInfo.get(0) : null; - } - - public void redirectFrom(@Nullable String from) { - redirectFrom = from; - } - - public void convertedFrom(@Nullable String from) { - convertedFrom = from; - } - - public void convertedTo(@Nullable String to) { - convertedTo = to; - } - - public void appendTitleFragment(@Nullable String fragment) { - title += "#" + fragment; - } - - public boolean checkWhetherFileIsUsedInWikis() { - if (globalUsages != null && globalUsages.size() > 0) { - return true; - } - - if (fileUsages == null || fileUsages.size() == 0) { - return false; - } - - final int totalCount = fileUsages.size(); - - /* Ignore usage under https://commons.wikimedia.org/wiki/User:Didym/Mobile_upload/ - which has been a gallery of all of our uploads since 2014 */ - for (final FileUsage fileUsage : fileUsages) { - if ( ! fileUsage.title().contains("User:Didym/Mobile upload")) { - return true; - } - } - - return false; - } - - public static class Revision { - @SerializedName("revid") private long revisionId; - private String user; - @SerializedName("contentformat") @NonNull private String contentFormat; - @SerializedName("contentmodel") @NonNull private String contentModel; - @SerializedName("timestamp") @NonNull private String timeStamp; - @NonNull private String content; - - @NonNull public String content() { - return content; - } - - @NonNull public String timeStamp() { - return StringUtils.defaultString(timeStamp); - } - - public long getRevisionId() { - return revisionId; - } - - @NonNull - public String getUser() { - return StringUtils.defaultString(user); - } - } - - public static class Coordinates { - @Nullable private Double lat; - @Nullable private Double lon; - - @Nullable public Double lat() { - return lat; - } - @Nullable public Double lon() { - return lon; - } - } - - public static class CategoryInfo { - private boolean hidden; - private int size; - private int pages; - private int files; - private int subcats; - public boolean isHidden() { - return hidden; - } - } - - static class Thumbnail { - private String source; - private int width; - private int height; - String source() { - return source; - } - } - - public static class GlobalUsage { - @SerializedName("title") private String title; - @SerializedName("wiki")private String wiki; - @SerializedName("url") private String url; - - public String getTitle() { - return title; - } - - public String getWiki() { - return wiki; - } - - public String getUrl() { - return url; - } - } - - public static class FileUsage { - @SerializedName("pageid") private int pageid; - @SerializedName("ns") private int ns; - @SerializedName("title") private String title; - - public int pageId() { - return pageid; - } - - public int ns() { - return ns; - } - - public String title() { - return title; - } - } - - public static class Category { - private int ns; - @SuppressWarnings("unused,NullableProblems") @Nullable private String title; - private boolean hidden; - - public int ns() { - return ns; - } - - @NonNull public String title() { - return StringUtils.defaultString(title); - } - - public boolean hidden() { - return hidden; - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.kt new file mode 100644 index 0000000000..74dfa511ee --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.kt @@ -0,0 +1,186 @@ +package fr.free.nrw.commons.wikidata.mwapi + +import androidx.annotation.VisibleForTesting +import com.google.gson.annotations.SerializedName +import fr.free.nrw.commons.wikidata.model.BaseModel +import fr.free.nrw.commons.wikidata.model.gallery.ImageInfo +import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.GlobalUsage + +/** + * A class representing a standard page object as returned by the MediaWiki API. + */ +class MwQueryPage : BaseModel() { + private val pageid = 0 + private val index = 0 + private var title: String? = null + private val categoryinfo: CategoryInfo? = null + private val revisions: List? = null + + @SerializedName("fileusage") + private val fileUsages: List? = null + + @SerializedName("globalusage") + private val globalUsages: List? = null + private val coordinates: MutableList? = null + private val categories: List? = null + private val thumbnail: Thumbnail? = null + private val description: String? = null + + @SerializedName("imageinfo") + private val imageInfo: List? = null + private var redirectFrom: String? = null + private var convertedFrom: String? = null + private var convertedTo: String? = null + + fun title(): String = title!! + + fun categoryInfo(): CategoryInfo = categoryinfo!! + + fun index(): Int = index + + fun revisions(): List? = revisions + + fun categories(): List? = categories + + // TODO: Handle null values in lists during deserialization, perhaps with a new + // @RequiredElements annotation and corresponding TypeAdapter + fun coordinates(): List? = coordinates?.filterNotNull() + + fun pageId(): Int = pageid + + fun thumbUrl(): String? = thumbnail?.source() + + fun description(): String? = description + + fun imageInfo(): ImageInfo? = imageInfo?.get(0) + + fun redirectFrom(from: String?) { + redirectFrom = from + } + + fun convertedFrom(from: String?) { + convertedFrom = from + } + + fun convertedTo(to: String?) { + convertedTo = to + } + + fun appendTitleFragment(fragment: String?) { + title += "#$fragment" + } + + fun checkWhetherFileIsUsedInWikis(): Boolean { + return checkWhetherFileIsUsedInWikis(globalUsages, fileUsages) + } + + class Revision { + @SerializedName("revid") + private val revisionId: Long = 0 + private val user: String? = null + @SerializedName("contentformat") + private val contentFormat: String? = null + @SerializedName("contentmodel") + private val contentModel: String? = null + @SerializedName("timestamp") + private val timeStamp: String? = null + private val content: String? = null + + fun revisionId(): Long = revisionId + + fun user(): String = user ?: "" + + fun content(): String = content!! + + fun timeStamp(): String = timeStamp ?: "" + } + + class Coordinates { + private val lat: Double? = null + private val lon: Double? = null + + fun lat(): Double? = lat + + fun lon(): Double? = lon + } + + class CategoryInfo { + val isHidden: Boolean = false + private val size = 0 + private val pages = 0 + private val files = 0 + private val subcats = 0 + } + + internal class Thumbnail { + private val source: String? = null + private val width = 0 + private val height = 0 + + fun source(): String? = source + } + + class GlobalUsage { + @SerializedName("title") + val title: String? = null + + @SerializedName("wiki") + val wiki: String? = null + + @SerializedName("url") + val url: String? = null + } + + class FileUsage { + @SerializedName("pageid") + private val pageid = 0 + + @SerializedName("ns") + private val ns = 0 + + @SerializedName("title") + private var title: String? = null + + fun pageId(): Int = pageid + + fun ns(): Int = ns + + fun title(): String = title ?: "" + + fun setTitle(value: String) { + title = value + } + } + + class Category { + private val ns = 0 + private val title: String? = null + private val hidden = false + + fun ns(): Int = ns + + fun title(): String = title ?: "" + + fun hidden(): Boolean = hidden + } +} + +@VisibleForTesting +fun checkWhetherFileIsUsedInWikis( + globalUsages: List?, + fileUsages: List? +): Boolean { + if (!globalUsages.isNullOrEmpty()) { + return true + } + + if (fileUsages.isNullOrEmpty()) { + return false + } + + /* Ignore usage under https://commons.wikimedia.org/wiki/User:Didym/Mobile_upload/ + which has been a gallery of all of our uploads since 2014 */ + return fileUsages.filterNot { + it.title().contains("User:Didym/Mobile upload") + }.isNotEmpty() +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.java deleted file mode 100644 index 871a2c31b0..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -import java.util.Map; - -public class MwQueryResponse extends MwResponse { - - @SerializedName("continue") @Nullable private Map continuation; - - @SerializedName("query") @Nullable private MwQueryResult query; - - @Nullable public Map continuation() { - return continuation; - } - - @Nullable public MwQueryResult query() { - return query; - } - - public boolean success() { - return query != null; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.kt new file mode 100644 index 0000000000..875a3cc2ea --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResponse.kt @@ -0,0 +1,15 @@ +package fr.free.nrw.commons.wikidata.mwapi + +import com.google.gson.annotations.SerializedName + +class MwQueryResponse : MwResponse() { + @SerializedName("continue") + private val continuation: Map? = null + private val query: MwQueryResult? = null + + fun continuation(): Map? = continuation + + fun query(): MwQueryResult? = query + + fun success(): Boolean = query != null +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.java deleted file mode 100644 index 303400160f..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.java +++ /dev/null @@ -1,187 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -import fr.free.nrw.commons.wikidata.json.PostProcessingTypeAdapter; -import org.apache.commons.lang3.StringUtils; -import fr.free.nrw.commons.wikidata.model.gallery.ImageInfo; -import fr.free.nrw.commons.wikidata.model.BaseModel; -import fr.free.nrw.commons.wikidata.model.notifications.Notification; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -public class MwQueryResult extends BaseModel implements PostProcessingTypeAdapter.PostProcessable { - @SerializedName("pages") @Nullable private List pages; - @Nullable private List redirects; - @Nullable private List converted; - @SerializedName("userinfo") private UserInfo userInfo; - @Nullable private List users; - @Nullable private Tokens tokens; - @Nullable private NotificationList notifications; - @SerializedName("allimages") @Nullable private List allImages; - - @Nullable public List pages() { - return pages; - } - - @Nullable public MwQueryPage firstPage() { - if (pages != null && pages.size() > 0) { - return pages.get(0); - } - return null; - } - - @NonNull - public List allImages() { - return allImages == null ? Collections.emptyList() : allImages; - } - - @Nullable public UserInfo userInfo() { - return userInfo; - } - - @Nullable public String csrfToken() { - return tokens != null ? tokens.csrf() : null; - } - - @Nullable public String loginToken() { - return tokens != null ? tokens.login() : null; - } - - @Nullable public NotificationList notifications() { - return notifications; - } - - @Nullable public ListUserResponse getUserResponse(@NonNull String userName) { - if (users != null) { - for (ListUserResponse user : users) { - // MediaWiki user names are case sensitive, but the first letter is always capitalized. - if (StringUtils.capitalize(userName).equals(user.name())) { - return user; - } - } - } - return null; - } - - @NonNull public Map images() { - Map result = new HashMap<>(); - if (pages != null) { - for (MwQueryPage page : pages) { - if (page.imageInfo() != null) { - result.put(page.title(), page.imageInfo()); - } - } - } - return result; - } - - @Override - public void postProcess() { - resolveConvertedTitles(); - resolveRedirectedTitles(); - } - - private void resolveRedirectedTitles() { - if (redirects == null || pages == null) { - return; - } - for (MwQueryPage page : pages) { - for (MwQueryResult.Redirect redirect : redirects) { - // TODO: Looks like result pages and redirects can also be matched on the "index" - // property. Confirm in the API docs and consider updating. - if (page.title().equals(redirect.to())) { - page.redirectFrom(redirect.from()); - if (redirect.toFragment() != null) { - page.appendTitleFragment(redirect.toFragment()); - } - } - } - } - } - - private void resolveConvertedTitles() { - if (converted == null || pages == null) { - return; - } - // noinspection ConstantConditions - for (MwQueryResult.ConvertedTitle convertedTitle : converted) { - // noinspection ConstantConditions - for (MwQueryPage page : pages) { - if (page.title().equals(convertedTitle.to())) { - page.convertedFrom(convertedTitle.from()); - page.convertedTo(convertedTitle.to()); - } - } - } - } - - private static class Redirect { - private int index; - @Nullable private String from; - @Nullable private String to; - @SerializedName("tofragment") @Nullable private String toFragment; - - @Nullable public String to() { - return to; - } - - @Nullable public String from() { - return from; - } - - @Nullable public String toFragment() { - return toFragment; - } - } - - public static class ConvertedTitle { - @Nullable private String from; - @Nullable private String to; - - @Nullable public String to() { - return to; - } - - @Nullable public String from() { - return from; - } - } - - private static class Tokens { - @SuppressWarnings("unused,NullableProblems") @SerializedName("csrftoken") - @Nullable private String csrf; - @SuppressWarnings("unused,NullableProblems") @SerializedName("createaccounttoken") - @Nullable private String createAccount; - @SuppressWarnings("unused,NullableProblems") @SerializedName("logintoken") - @Nullable private String login; - - @Nullable private String csrf() { - return csrf; - } - - @Nullable private String createAccount() { - return createAccount; - } - - @Nullable private String login() { - return login; - } - } - - public static class NotificationList { - @Nullable - private List list; - @Nullable - public List list() { - return list; - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.kt new file mode 100644 index 0000000000..8c898d848f --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryResult.kt @@ -0,0 +1,134 @@ +package fr.free.nrw.commons.wikidata.mwapi + +import com.google.gson.annotations.SerializedName +import fr.free.nrw.commons.wikidata.json.PostProcessingTypeAdapter.PostProcessable +import fr.free.nrw.commons.wikidata.model.BaseModel +import fr.free.nrw.commons.wikidata.model.gallery.ImageInfo +import fr.free.nrw.commons.wikidata.model.notifications.Notification +import org.apache.commons.lang3.StringUtils + +class MwQueryResult : BaseModel(), PostProcessable { + private val pages: List? = null + private val redirects: List? = null + private val converted: List? = null + + @SerializedName("userinfo") + private val userInfo: UserInfo? = null + private val users: List? = null + private val tokens: Tokens? = null + private val notifications: NotificationList? = null + + @SerializedName("allimages") + private val allImages: List? = null + + fun pages(): List? = pages + + fun firstPage(): MwQueryPage? = pages?.firstOrNull() + + fun allImages(): List = allImages ?: emptyList() + + fun userInfo(): UserInfo? = userInfo + + fun csrfToken(): String? = tokens?.csrf() + + fun loginToken(): String? = tokens?.login() + + fun notifications(): NotificationList? = notifications + + fun getUserResponse(userName: String): ListUserResponse? = + users?.find { StringUtils.capitalize(userName) == it.name() } + + fun images() = buildMap { + pages?.forEach { page -> + page.imageInfo()?.let { + put(page.title(), it) + } + } + } + + override fun postProcess() { + resolveConvertedTitles() + resolveRedirectedTitles() + } + + private fun resolveRedirectedTitles() { + if (redirects == null || pages == null) { + return + } + + pages.forEach { page -> + redirects.forEach { redirect -> + // TODO: Looks like result pages and redirects can also be matched on the "index" + // property. Confirm in the API docs and consider updating. + if (page.title() == redirect.to()) { + page.redirectFrom(redirect.from()) + if (redirect.toFragment() != null) { + page.appendTitleFragment(redirect.toFragment()) + } + } + } + } + } + + private fun resolveConvertedTitles() { + if (converted == null || pages == null) { + return + } + + converted.forEach { convertedTitle -> + pages.forEach { page -> + if (page.title() == convertedTitle.to()) { + page.convertedFrom(convertedTitle.from()) + page.convertedTo(convertedTitle.to()) + } + } + } + } + + private class Redirect { + private val index = 0 + private val from: String? = null + private val to: String? = null + + @SerializedName("tofragment") + private val toFragment: String? = null + + fun to(): String? = to + + fun from(): String? = from + + fun toFragment(): String? = toFragment + } + + class ConvertedTitle { + private val from: String? = null + private val to: String? = null + + fun to(): String? = to + + fun from(): String? = from + } + + private class Tokens { + @SerializedName("csrftoken") + private val csrf: String? = null + + @SerializedName("createaccounttoken") + private val createAccount: String? = null + + @SerializedName("logintoken") + private val login: String? = null + + fun csrf(): String? = csrf + + fun createAccount(): String? = createAccount + + fun login(): String? = login + } + + class NotificationList { + private val list: List? = null + + fun list(): List? = list + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.java deleted file mode 100644 index 52662cd12c..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -import fr.free.nrw.commons.wikidata.json.PostProcessingTypeAdapter; -import fr.free.nrw.commons.wikidata.model.BaseModel; - -import java.util.List; - -public abstract class MwResponse extends BaseModel implements PostProcessingTypeAdapter.PostProcessable { - @SuppressWarnings({"unused"}) @Nullable private List errors; - @SuppressWarnings("unused,NullableProblems") @SerializedName("servedby") @NonNull private String servedBy; - - @Override - public void postProcess() { - if (errors != null && !errors.isEmpty()) { - throw new MwException(errors.get(0), errors); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.kt new file mode 100644 index 0000000000..02a637b142 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwResponse.kt @@ -0,0 +1,18 @@ +package fr.free.nrw.commons.wikidata.mwapi + +import com.google.gson.annotations.SerializedName +import fr.free.nrw.commons.wikidata.json.PostProcessingTypeAdapter.PostProcessable +import fr.free.nrw.commons.wikidata.model.BaseModel + +abstract class MwResponse : BaseModel(), PostProcessable { + private val errors: List? = null + + @SerializedName("servedby") + private val servedBy: String? = null + + override fun postProcess() { + if (!errors.isNullOrEmpty()) { + throw MwException(errors[0], errors) + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.java b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.java deleted file mode 100644 index 4798a19b4b..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.java +++ /dev/null @@ -1,29 +0,0 @@ -package fr.free.nrw.commons.wikidata.mwapi; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.apache.commons.lang3.StringUtils; -import fr.free.nrw.commons.wikidata.model.BaseModel; - -/** - * Gson POJO for a MediaWiki API error. - */ -public class MwServiceError extends BaseModel { - - @Nullable private String code; - @Nullable private String text; - - @NonNull public String getTitle() { - return StringUtils.defaultString(code); - } - - @NonNull public String getDetails() { - return StringUtils.defaultString(text); - } - - @Nullable - public String getCode() { - return code; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.kt new file mode 100644 index 0000000000..1408efbef5 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwServiceError.kt @@ -0,0 +1,18 @@ +package fr.free.nrw.commons.wikidata.mwapi + +import fr.free.nrw.commons.wikidata.model.BaseModel +import org.apache.commons.lang3.StringUtils + +/** + * Gson POJO for a MediaWiki API error. + */ +class MwServiceError : BaseModel() { + val code: String? = null + private val text: String? = null + + val title: String + get() = code ?: "" + + val details: String + get() = text ?: "" +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewControllerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewControllerTest.kt index 059583f8a3..08237c5d92 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewControllerTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewControllerTest.kt @@ -139,7 +139,7 @@ class ReviewControllerTest { @Test fun testSendThanks() { shadowOf(Looper.getMainLooper()).idle() - whenever(firstRevision.revisionId).thenReturn(1) + whenever(firstRevision.revisionId()).thenReturn(1) Whitebox.setInternalState(controller, "firstRevision", firstRevision) controller.sendThanks(activity) assertEquals( diff --git a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt index f980152dc8..a0e53cfabb 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt @@ -95,11 +95,11 @@ class ReviewHelperTest { @Test fun getFirstRevisionOfFile() { val rev1 = mock() - whenever(rev1.user).thenReturn("TestUser") - whenever(rev1.revisionId).thenReturn(1L) + whenever(rev1.user()).thenReturn("TestUser") + whenever(rev1.revisionId()).thenReturn(1L) val rev2 = mock() - whenever(rev2.user).thenReturn("TestUser") - whenever(rev2.revisionId).thenReturn(2L) + whenever(rev2.user()).thenReturn("TestUser") + whenever(rev2.revisionId()).thenReturn(2L) val page = setupMedia("Test.jpg", rev1, rev2) whenever(mwQueryResult.firstPage()).thenReturn(page) @@ -107,7 +107,7 @@ class ReviewHelperTest { val firstRevisionOfFile = reviewHelper.getFirstRevisionOfFile("Test.jpg").blockingFirst() - assertEquals(1, firstRevisionOfFile.revisionId) + assertEquals(1, firstRevisionOfFile.revisionId()) } @Test diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/mwapi/MwQueryPageTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/mwapi/MwQueryPageTest.kt new file mode 100644 index 0000000000..4bd455d745 --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/mwapi/MwQueryPageTest.kt @@ -0,0 +1,48 @@ +package fr.free.nrw.commons.wikidata.mwapi + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class MwQueryPageTest { + private val didymUsage = MwQueryPage.FileUsage().apply { + setTitle("User:Didym/Mobile upload") + } + + @Test + fun checkWhetherFileIsUsedInWikis_nullGlobalUsages() { + assertFalse(checkWhetherFileIsUsedInWikis(null, null)) + } + + @Test + fun checkWhetherFileIsUsedInWikis_emptyGlobalUsages() { + assertFalse(checkWhetherFileIsUsedInWikis(emptyList(), null)) + } + + @Test + fun checkWhetherFileIsUsedInWikis_emptyFileUsage() { + assertFalse(checkWhetherFileIsUsedInWikis(emptyList(), emptyList())) + } + + @Test + fun checkWhetherFileIsUsedInWikis_singleGlobalUsages() { + assertTrue(checkWhetherFileIsUsedInWikis(listOf(MwQueryPage.GlobalUsage()), null)) + } + + @Test + fun checkWhetherFileIsUsedInWikis_singleFileUsageContainsDidym() { + assertFalse(checkWhetherFileIsUsedInWikis(null, listOf(didymUsage))) + } + + @Test + fun checkWhetherFileIsUsedInWikis_didymIgnoredInList() { + assertTrue( + checkWhetherFileIsUsedInWikis( + null, listOf( + didymUsage, MwQueryPage.FileUsage().apply { setTitle("somewhere else") } + ) + ) + ) + } + +} \ No newline at end of file