mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 22:03:55 +01:00
Convert API clients to kotlin (#5567)
* Convert UserClient to kotlin * Added tests for WikiBaseClient * Removed superfluous dao tests in review helper and got the proper ReviewDaoTest running in the unit test suite * Improved tests for ReviewHelper * Convert the ReviewHelper to kotlin * Convert the WikiBaseClient to kotlin * Convert the WikidataClient to kotlin
This commit is contained in:
parent
c43405267a
commit
728712c4e1
15 changed files with 496 additions and 526 deletions
|
|
@ -1,166 +0,0 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.Collections;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage;
|
||||
import timber.log.Timber;
|
||||
|
||||
@Singleton
|
||||
public class ReviewHelper {
|
||||
|
||||
private static final String[] imageExtensions = new String[]{".jpg", ".jpeg", ".png"};
|
||||
|
||||
private final MediaClient mediaClient;
|
||||
private final ReviewInterface reviewInterface;
|
||||
|
||||
@Inject
|
||||
ReviewDao dao;
|
||||
|
||||
@Inject
|
||||
public ReviewHelper(MediaClient mediaClient, ReviewInterface reviewInterface) {
|
||||
this.mediaClient = mediaClient;
|
||||
this.reviewInterface = reviewInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches recent changes from MediaWiki API
|
||||
* Calls the API to get the latest 50 changes
|
||||
* When more results are available, the query gets continued beyond this range
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Observable<MwQueryPage> getRecentChanges() {
|
||||
return reviewInterface.getRecentChanges()
|
||||
.map(mwQueryResponse -> mwQueryResponse.query().pages())
|
||||
.map(recentChanges -> {
|
||||
Collections.shuffle(recentChanges);
|
||||
return recentChanges;
|
||||
})
|
||||
.flatMapIterable(changes -> changes)
|
||||
.filter(recentChange -> isChangeReviewable(recentChange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random file change for review.
|
||||
* - Picks a random file from those changes
|
||||
* - Checks if the file is nominated for deletion
|
||||
* - Retries upto 5 times for getting a file which is not nominated for deletion
|
||||
*
|
||||
* @return Random file change
|
||||
*/
|
||||
public Single<Media> getRandomMedia() {
|
||||
return getRecentChanges()
|
||||
.flatMapSingle(change -> getRandomMediaFromRecentChange(change))
|
||||
.filter(media -> !StringUtils.isBlank(media.getFilename())
|
||||
&& !getReviewStatus(media.getPageId()) // Check if the image has already been shown to the user
|
||||
)
|
||||
.firstOrError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a proper Media object if the file is not already nominated for deletion
|
||||
* Else it returns an empty Media object
|
||||
*
|
||||
* @param recentChange
|
||||
* @return
|
||||
*/
|
||||
private Single<Media> getRandomMediaFromRecentChange(MwQueryPage recentChange) {
|
||||
return Single.just(recentChange)
|
||||
.flatMap(change -> mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + change.title()))
|
||||
.flatMap(isDeleted -> {
|
||||
if (isDeleted) {
|
||||
return Single.error(new Exception(recentChange.title() + " is deleted"));
|
||||
}
|
||||
return mediaClient.getMedia(recentChange.title());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the image exists in the reviewed images entity
|
||||
*
|
||||
* @param image
|
||||
* @return
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Boolean getReviewStatus(String image){
|
||||
if(dao == null){
|
||||
return false;
|
||||
}
|
||||
return Observable.fromCallable(()-> dao.isReviewedAlready(image))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread()).blockingSingle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first revision of the file from filename
|
||||
*
|
||||
* @param filename
|
||||
* @return
|
||||
*/
|
||||
public Observable<MwQueryPage.Revision> getFirstRevisionOfFile(String filename) {
|
||||
return reviewInterface.getFirstRevisionOfFile(filename)
|
||||
.map(response -> response.query().firstPage().revisions().get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks Whether Given File is used in any Wiki page or not
|
||||
* by calling api for given file
|
||||
*
|
||||
* @param filename
|
||||
* @return
|
||||
*/
|
||||
Observable<Boolean> checkFileUsage(final String filename) {
|
||||
return reviewInterface.getGlobalUsageInfo(filename)
|
||||
.map(mwQueryResponse -> mwQueryResponse.query().firstPage()
|
||||
.checkWhetherFileIsUsedInWikis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the change is reviewable or not.
|
||||
* - checks the type and revisionId of the change
|
||||
* - checks supported image extensions
|
||||
*
|
||||
* @param recentChange
|
||||
* @return
|
||||
*/
|
||||
private boolean isChangeReviewable(MwQueryPage recentChange) {
|
||||
for (String extension : imageExtensions) {
|
||||
if (recentChange.title().endsWith(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds reviewed/skipped images to the database
|
||||
*
|
||||
* @param imageId
|
||||
*/
|
||||
public void addViewedImagesToDB(String imageId) {
|
||||
Completable.fromAction(() -> dao.insert(new ReviewEntity(imageId)))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
// Inserted successfully
|
||||
Timber.i("Image inserted successfully.");
|
||||
},
|
||||
throwable -> {
|
||||
Timber.e("Image not inserted into the reviewed images database");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
138
app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt
Normal file
138
app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package fr.free.nrw.commons.review
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.Revision
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import timber.log.Timber
|
||||
import java.util.Collections
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ReviewHelper @Inject constructor(
|
||||
private val mediaClient: MediaClient,
|
||||
private val reviewInterface: ReviewInterface
|
||||
) {
|
||||
@JvmField @Inject var dao: ReviewDao? = null
|
||||
|
||||
/**
|
||||
* Fetches recent changes from MediaWiki API
|
||||
* Calls the API to get the latest 50 changes
|
||||
* When more results are available, the query gets continued beyond this range
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private fun getRecentChanges() = reviewInterface.getRecentChanges()
|
||||
.map { it.query()?.pages() }
|
||||
.map(MutableList<MwQueryPage>::shuffled)
|
||||
.flatMapIterable { changes: List<MwQueryPage>? -> changes }
|
||||
.filter { isChangeReviewable(it) }
|
||||
|
||||
/**
|
||||
* Gets a random file change for review. Checks if the image has already been shown to the user
|
||||
* - Picks a random file from those changes
|
||||
* - Checks if the file is nominated for deletion
|
||||
* - Retries upto 5 times for getting a file which is not nominated for deletion
|
||||
*
|
||||
* @return Random file change
|
||||
*/
|
||||
fun getRandomMedia(): Single<Media> = getRecentChanges()
|
||||
.flatMapSingle(::getRandomMediaFromRecentChange)
|
||||
.filter { !it.filename.isNullOrBlank() && !getReviewStatus(it.pageId) }
|
||||
.firstOrError()
|
||||
|
||||
/**
|
||||
* Returns a proper Media object if the file is not already nominated for deletion
|
||||
* Else it returns an empty Media object
|
||||
*
|
||||
* @param recentChange
|
||||
* @return
|
||||
*/
|
||||
private fun getRandomMediaFromRecentChange(recentChange: MwQueryPage) =
|
||||
Single.just(recentChange)
|
||||
.flatMap { mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/${it.title()}") }
|
||||
.flatMap {
|
||||
if (it) {
|
||||
Single.error(Exception("${recentChange.title()} is deleted"))
|
||||
} else {
|
||||
mediaClient.getMedia(recentChange.title())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the image exists in the reviewed images entity
|
||||
*
|
||||
* @param image
|
||||
* @return
|
||||
*/
|
||||
fun getReviewStatus(image: String?): Boolean =
|
||||
dao?.isReviewedAlready(image) ?: false
|
||||
|
||||
/**
|
||||
* Gets the first revision of the file from filename
|
||||
*
|
||||
* @param filename
|
||||
* @return
|
||||
*/
|
||||
fun getFirstRevisionOfFile(filename: String?): Observable<Revision> =
|
||||
reviewInterface.getFirstRevisionOfFile(filename)
|
||||
.map { it.query()?.firstPage()?.revisions()?.get(0) }
|
||||
|
||||
/**
|
||||
* Checks Whether Given File is used in any Wiki page or not
|
||||
* by calling api for given file
|
||||
*
|
||||
* @param filename
|
||||
* @return
|
||||
*/
|
||||
fun checkFileUsage(filename: String?): Observable<Boolean> =
|
||||
reviewInterface.getGlobalUsageInfo(filename)
|
||||
.map { it.query()?.firstPage()?.checkWhetherFileIsUsedInWikis() }
|
||||
|
||||
/**
|
||||
* Checks if the change is reviewable or not.
|
||||
* - checks the type and revisionId of the change
|
||||
* - checks supported image extensions
|
||||
*
|
||||
* @param recentChange
|
||||
* @return
|
||||
*/
|
||||
private fun isChangeReviewable(recentChange: MwQueryPage): Boolean {
|
||||
for (extension in imageExtensions) {
|
||||
if (recentChange.title().endsWith(extension)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds reviewed/skipped images to the database
|
||||
*
|
||||
* @param imageId
|
||||
*/
|
||||
fun addViewedImagesToDB(imageId: String?) {
|
||||
Completable.fromAction { dao!!.insert(ReviewEntity(imageId)) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
// Inserted successfully
|
||||
Timber.i("Image inserted successfully.")
|
||||
}
|
||||
) { throwable: Throwable? -> Timber.e("Image not inserted into the reviewed images database") }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val imageExtensions = arrayOf(".jpg", ".jpeg", ".png")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue