mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-11-02 15:53:55 +01:00
Merge 8ec6c2f8cd into cdc4f89da5
This commit is contained in:
commit
b4e3d73fee
2 changed files with 299 additions and 47 deletions
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.review;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -11,6 +12,7 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
|
|
@ -22,19 +24,25 @@ import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class ReviewActivity extends BaseActivity {
|
public class ReviewActivity extends BaseActivity {
|
||||||
|
|
||||||
|
|
||||||
private ActivityReviewBinding binding;
|
private ActivityReviewBinding binding;
|
||||||
|
|
||||||
MediaDetailFragment mediaDetailFragment;
|
MediaDetailFragment mediaDetailFragment;
|
||||||
public ReviewPagerAdapter reviewPagerAdapter;
|
public ReviewPagerAdapter reviewPagerAdapter;
|
||||||
public ReviewController reviewController;
|
public ReviewController reviewController;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ReviewHelper reviewHelper;
|
ReviewHelper reviewHelper;
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -53,6 +61,20 @@ public class ReviewActivity extends BaseActivity {
|
||||||
final String SAVED_MEDIA = "saved_media";
|
final String SAVED_MEDIA = "saved_media";
|
||||||
private Media media;
|
private Media media;
|
||||||
|
|
||||||
|
private List<Media> cachedMedia = new ArrayList<>();
|
||||||
|
|
||||||
|
/** Constants for managing media cache in the review activity */
|
||||||
|
// Name of SharedPreferences file for storing review activity preferences
|
||||||
|
private static final String PREF_NAME = "ReviewActivityPrefs";
|
||||||
|
// Key for storing the timestamp of last cache update
|
||||||
|
private static final String LAST_CACHE_TIME_KEY = "lastCacheTime";
|
||||||
|
|
||||||
|
// Maximum number of media files to store in cache
|
||||||
|
private static final int CACHE_SIZE = 5;
|
||||||
|
// Cache expiration time in milliseconds (24 hours)
|
||||||
|
|
||||||
|
private static final long CACHE_EXPIRY_TIME = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
|
|
@ -99,11 +121,22 @@ public class ReviewActivity extends BaseActivity {
|
||||||
Drawable d[]=binding.skipImage.getCompoundDrawablesRelative();
|
Drawable d[]=binding.skipImage.getCompoundDrawablesRelative();
|
||||||
d[2].setColorFilter(getApplicationContext().getResources().getColor(R.color.button_blue), PorterDuff.Mode.SRC_IN);
|
d[2].setColorFilter(getApplicationContext().getResources().getColor(R.color.button_blue), PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the previous state of the activity or initializes a new review session.
|
||||||
|
* Checks if there's a saved media state from a previous session (e.g., before screen rotation).
|
||||||
|
* If found, restores the last viewed image and its detail view.
|
||||||
|
* Otherwise, starts a new random image review session.
|
||||||
|
*
|
||||||
|
* @param savedInstanceState Bundle containing the activity's previously saved state, if any
|
||||||
|
*/
|
||||||
if (savedInstanceState != null && savedInstanceState.getParcelable(SAVED_MEDIA) != null) {
|
if (savedInstanceState != null && savedInstanceState.getParcelable(SAVED_MEDIA) != null) {
|
||||||
updateImage(savedInstanceState.getParcelable(SAVED_MEDIA)); // Use existing media if we have one
|
// Restore the previously viewed image if state exists
|
||||||
|
updateImage(savedInstanceState.getParcelable(SAVED_MEDIA));
|
||||||
|
// Restore media detail view (handles configuration changes like screen rotation)
|
||||||
setUpMediaDetailOnOrientation();
|
setUpMediaDetailOnOrientation();
|
||||||
} else {
|
} else {
|
||||||
runRandomizer(); //Run randomizer whenever everything is ready so that a first random image will be added
|
// Start fresh review session with a random image
|
||||||
|
runRandomizer();
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.skipImage.setOnClickListener(view -> {
|
binding.skipImage.setOnClickListener(view -> {
|
||||||
|
|
@ -131,36 +164,190 @@ public class ReviewActivity extends BaseActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the process of loading a random media file for review.
|
||||||
|
* This method:
|
||||||
|
* - Resets the UI state
|
||||||
|
* - Shows loading indicator
|
||||||
|
* - Manages media cache
|
||||||
|
* - Either loads from cache or fetches new media
|
||||||
|
*
|
||||||
|
* The method is annotated with @SuppressLint("CheckResult") as the Observable
|
||||||
|
* subscription is handled through CompositeDisposable in the implementation.
|
||||||
|
*
|
||||||
|
* @return true indicating successful initiation of the randomization process
|
||||||
|
*/
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
public boolean runRandomizer() {
|
public boolean runRandomizer() {
|
||||||
|
// Reset flag for tracking presence of non-hidden categories
|
||||||
hasNonHiddenCategories = false;
|
hasNonHiddenCategories = false;
|
||||||
|
// Display loading indicator while fetching media
|
||||||
binding.pbReviewImage.setVisibility(View.VISIBLE);
|
binding.pbReviewImage.setVisibility(View.VISIBLE);
|
||||||
|
// Reset view pager to first page
|
||||||
binding.viewPagerReview.setCurrentItem(0);
|
binding.viewPagerReview.setCurrentItem(0);
|
||||||
// Finds non-hidden categories from Media instance
|
|
||||||
compositeDisposable.add(reviewHelper.getRandomMedia()
|
// Check cache status and determine source of next media
|
||||||
|
if (cachedMedia.isEmpty() || isCacheExpired()) {
|
||||||
|
// Fetch and cache new media if cache is empty or expired
|
||||||
|
fetchAndCacheMedia();
|
||||||
|
} else {
|
||||||
|
// Use next media file from existing cache
|
||||||
|
processNextCachedMedia();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch checks whether multiple files from the cache are used in wikis.
|
||||||
|
* This is a more efficient way to process multiple files compared to checking them one by one.
|
||||||
|
*
|
||||||
|
* @param mediaList List of Media objects to check for usage
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Batch checks whether multiple files from the cache are used in wikis.
|
||||||
|
* This is a more efficient way to process multiple files compared to checking them one by one.
|
||||||
|
*
|
||||||
|
* @param mediaList List of Media objects to check for usage
|
||||||
|
*/
|
||||||
|
private void batchCheckFilesUsage(List<Media> mediaList) {
|
||||||
|
// Extract filenames from media objects
|
||||||
|
List<String> filenames = new ArrayList<>();
|
||||||
|
for (Media media : mediaList) {
|
||||||
|
if (media.getFilename() != null) {
|
||||||
|
filenames.add(media.getFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compositeDisposable.add(
|
||||||
|
reviewHelper.checkFileUsageBatch(filenames)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(this::checkWhetherFileIsUsedInWikis));
|
.toList()
|
||||||
return true;
|
.subscribe(results -> {
|
||||||
|
// Process each result
|
||||||
|
for (kotlin.Pair<String, Boolean> result : results) {
|
||||||
|
String filename = result.getFirst();
|
||||||
|
Boolean isUsed = result.getSecond();
|
||||||
|
|
||||||
|
// Find corresponding media object
|
||||||
|
for (Media media : mediaList) {
|
||||||
|
if (filename.equals(media.getFilename())) {
|
||||||
|
if (!isUsed) {
|
||||||
|
// If file is not used, proceed with category check
|
||||||
|
findNonHiddenCategories(media);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this::handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and caches new media files for review.
|
||||||
|
* Uses RxJava to:
|
||||||
|
* - Generate a range of indices for the desired cache size
|
||||||
|
* - Fetch random media files asynchronously
|
||||||
|
* - Handle thread scheduling between IO and UI operations
|
||||||
|
* - Store the fetched media in cache
|
||||||
|
*
|
||||||
|
* The operation is added to compositeDisposable for proper lifecycle management.
|
||||||
|
*/
|
||||||
|
private void fetchAndCacheMedia() {
|
||||||
|
compositeDisposable.add(
|
||||||
|
Observable.range(0, CACHE_SIZE)
|
||||||
|
.flatMap(i -> reviewHelper.getRandomMedia().toObservable())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.toList()
|
||||||
|
.subscribe(mediaList -> {
|
||||||
|
// Clear existing cache
|
||||||
|
cachedMedia.clear();
|
||||||
|
|
||||||
|
// Start batch check process
|
||||||
|
batchCheckFilesUsage(mediaList);
|
||||||
|
|
||||||
|
// Update cache with new media
|
||||||
|
cachedMedia.addAll(mediaList);
|
||||||
|
updateLastCacheTime();
|
||||||
|
|
||||||
|
// Process first media item if available
|
||||||
|
if (!cachedMedia.isEmpty()) {
|
||||||
|
processNextCachedMedia();
|
||||||
|
}
|
||||||
|
}, this::handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the next media file from the cache.
|
||||||
|
* If cache is not empty, removes and processes the first media file.
|
||||||
|
* If cache is empty, triggers a new fetch operation.
|
||||||
|
*
|
||||||
|
* This method ensures continuous flow of media files for review
|
||||||
|
* while maintaining the cache mechanism.
|
||||||
|
*/
|
||||||
|
private void processNextCachedMedia() {
|
||||||
|
if (!cachedMedia.isEmpty()) {
|
||||||
|
// Remove and get the first media from cache
|
||||||
|
Media media = cachedMedia.remove(0);
|
||||||
|
|
||||||
|
checkWhetherFileIsUsedInWikis(media);
|
||||||
|
} else {
|
||||||
|
// Refill cache if empty
|
||||||
|
fetchAndCacheMedia();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current cache has expired.
|
||||||
|
* Cache expiration is determined by comparing the last cache time
|
||||||
|
* with the current time against the configured expiry duration.
|
||||||
|
*
|
||||||
|
* @return true if cache has expired, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean isCacheExpired() {
|
||||||
|
// Get shared preferences instance
|
||||||
|
SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
long lastCacheTime = prefs.getLong(LAST_CACHE_TIME_KEY, 0);
|
||||||
|
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
return (currentTime - lastCacheTime) > CACHE_EXPIRY_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the timestamp of the last cache operation.
|
||||||
|
* Stores the current time in SharedPreferences to track
|
||||||
|
* cache freshness for future operations.
|
||||||
|
*/
|
||||||
|
private void updateLastCacheTime() {
|
||||||
|
|
||||||
|
SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
// Store current timestamp as last cache time
|
||||||
|
editor.putLong(LAST_CACHE_TIME_KEY, System.currentTimeMillis());
|
||||||
|
// Apply changes asynchronously
|
||||||
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether media is used or not in any Wiki Page
|
* Check whether media is used or not in any Wiki Page
|
||||||
*/
|
*/
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private void checkWhetherFileIsUsedInWikis(final Media media) {
|
private void checkWhetherFileIsUsedInWikis(final Media media) {
|
||||||
compositeDisposable.add(reviewHelper.checkFileUsage(media.getFilename())
|
compositeDisposable.add(reviewHelper.checkFileUsage(media.getFilename())
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(result -> {
|
.subscribe(result -> {
|
||||||
// result false indicates media is not used in any wiki
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
// Finds non-hidden categories from Media instance
|
|
||||||
findNonHiddenCategories(media);
|
findNonHiddenCategories(media);
|
||||||
} else {
|
} else {
|
||||||
runRandomizer();
|
processNextCachedMedia();
|
||||||
}
|
}
|
||||||
}));
|
}, this::handleError));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -181,7 +368,6 @@ public class ReviewActivity extends BaseActivity {
|
||||||
updateImage(media);
|
updateImage(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private void updateImage(Media media) {
|
private void updateImage(Media media) {
|
||||||
reviewHelper.addViewedImagesToDB(media.getPageId());
|
reviewHelper.addViewedImagesToDB(media.getPageId());
|
||||||
this.media = media;
|
this.media = media;
|
||||||
|
|
@ -191,27 +377,26 @@ public class ReviewActivity extends BaseActivity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//If The Media User and Current Session Username is same then Skip the Image
|
|
||||||
if (media.getUser() != null && media.getUser().equals(AccountUtil.getUserName(getApplicationContext()))) {
|
if (media.getUser() != null && media.getUser().equals(AccountUtil.getUserName(getApplicationContext()))) {
|
||||||
runRandomizer();
|
processNextCachedMedia();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.reviewImageView.setImageURI(media.getImageUrl());
|
binding.reviewImageView.setImageURI(media.getImageUrl());
|
||||||
|
|
||||||
reviewController.onImageRefreshed(media); //file name is updated
|
reviewController.onImageRefreshed(media);
|
||||||
compositeDisposable.add(reviewHelper.getFirstRevisionOfFile(fileName)
|
compositeDisposable.add(reviewHelper.getFirstRevisionOfFile(fileName)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(revision -> {
|
.subscribe(revision -> {
|
||||||
reviewController.firstRevision = revision;
|
reviewController.firstRevision = revision;
|
||||||
reviewPagerAdapter.updateFileInformation();
|
reviewPagerAdapter.updateFileInformation();
|
||||||
@SuppressLint({"StringFormatInvalid", "LocalSuppress"}) String caption = String.format(getString(R.string.review_is_uploaded_by), fileName, revision.getUser());
|
String caption = String.format(getString(R.string.review_is_uploaded_by), fileName, revision.getUser());
|
||||||
binding.tvImageCaption.setText(caption);
|
binding.tvImageCaption.setText(caption);
|
||||||
binding.pbReviewImage.setVisibility(View.GONE);
|
binding.pbReviewImage.setVisibility(View.GONE);
|
||||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
reviewImageFragment = getInstanceOfReviewImageFragment();
|
||||||
reviewImageFragment.enableButtons();
|
reviewImageFragment.enableButtons();
|
||||||
}));
|
}, this::handleError));
|
||||||
binding.viewPagerReview.setCurrentItem(0);
|
binding.viewPagerReview.setCurrentItem(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,6 +443,21 @@ public class ReviewActivity extends BaseActivity {
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Handles errors that occur during media processing operations.
|
||||||
|
* This is a generic error handler that:
|
||||||
|
* - Hides the loading indicator
|
||||||
|
* - Shows a user-friendly error message via Snackbar
|
||||||
|
*
|
||||||
|
* Used as error callback for RxJava operations and other async tasks.
|
||||||
|
*
|
||||||
|
* @param error The Throwable that was caught during operation
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable error) {
|
||||||
|
binding.pbReviewImage.setVisibility(View.GONE);
|
||||||
|
// Show error message to user via Snackbar
|
||||||
|
ViewUtil.showShortSnackbar(binding.drawerLayout, R.string.error_review);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,15 @@ class ReviewHelper
|
||||||
@JvmField @Inject
|
@JvmField @Inject
|
||||||
var dao: ReviewDao? = null
|
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
|
* Fetches recent changes from MediaWiki API
|
||||||
*
|
* Calls the API to get the latest 50 changes
|
||||||
* @return
|
* When more results are available, the query gets continued beyond this range
|
||||||
*/
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private fun getRecentChanges() =
|
private fun getRecentChanges() =
|
||||||
reviewInterface
|
reviewInterface
|
||||||
.getRecentChanges()
|
.getRecentChanges()
|
||||||
|
|
@ -38,19 +40,40 @@ class ReviewHelper
|
||||||
.flatMapIterable { changes: List<MwQueryPage>? -> changes }
|
.flatMapIterable { changes: List<MwQueryPage>? -> changes }
|
||||||
.filter { isChangeReviewable(it) }
|
.filter { isChangeReviewable(it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a random file change for review. Checks if the image has already been shown to the user
|
* Gets multiple random media items for review.
|
||||||
* - Picks a random file from those changes
|
* - Fetches recent changes and filters them
|
||||||
* - Checks if the file is nominated for deletion
|
* - Checks if files are nominated for deletion
|
||||||
* - Retries upto 5 times for getting a file which is not nominated for deletion
|
* - Filters out already reviewed images
|
||||||
*
|
*
|
||||||
* @return Random file change
|
* @param count Number of media items to fetch
|
||||||
*/
|
* @return Observable of Media items
|
||||||
fun getRandomMedia(): Single<Media> =
|
*/
|
||||||
getRecentChanges()
|
fun getRandomMediaBatch(count: Int): Observable<Media> =
|
||||||
.flatMapSingle(::getRandomMediaFromRecentChange)
|
getRecentChanges()
|
||||||
.filter { !it.filename.isNullOrBlank() && !getReviewStatus(it.pageId) }
|
.flatMapSingle(::getRandomMediaFromRecentChange)
|
||||||
.firstOrError()
|
.filter { media ->
|
||||||
|
!media.filename.isNullOrBlank() &&
|
||||||
|
!getReviewStatus(media.pageId)
|
||||||
|
}
|
||||||
|
.take(count.toLong())
|
||||||
|
.onErrorResumeNext { error: Throwable ->
|
||||||
|
Timber.e(error, "Error getting random media batch")
|
||||||
|
Observable.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a random file change for review.
|
||||||
|
*
|
||||||
|
* @return Random file change
|
||||||
|
*/
|
||||||
|
fun getRandomMedia(): Single<Media> =
|
||||||
|
getRandomMediaBatch(1)
|
||||||
|
.firstOrError()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a proper Media object if the file is not already nominated for deletion
|
* Returns a proper Media object if the file is not already nominated for deletion
|
||||||
|
|
@ -108,6 +131,34 @@ class ReviewHelper
|
||||||
.getGlobalUsageInfo(filename)
|
.getGlobalUsageInfo(filename)
|
||||||
.map { it.query()?.firstPage()?.checkWhetherFileIsUsedInWikis() }
|
.map { it.query()?.firstPage()?.checkWhetherFileIsUsedInWikis() }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch checks whether multiple files are being used in any wiki pages.
|
||||||
|
* This method processes a list of filenames in parallel using RxJava Observables.
|
||||||
|
*
|
||||||
|
* @param filenames A list of filenames to check for usage
|
||||||
|
* @return Observable emitting pairs of filename and usage status:
|
||||||
|
* - The String represents the filename
|
||||||
|
* - The Boolean indicates whether the file is used (true) or not (false)
|
||||||
|
* If an error occurs during processing, it will log the error and emit an empty Observable
|
||||||
|
*/
|
||||||
|
fun checkFileUsageBatch(filenames: List<String>): Observable<Pair<String, Boolean>> =
|
||||||
|
// Convert the list of filenames into an Observable stream
|
||||||
|
Observable.fromIterable(filenames)
|
||||||
|
// For each filename, check its usage and pair it with the result
|
||||||
|
.flatMap { filename ->
|
||||||
|
checkFileUsage(filename)
|
||||||
|
// Create a pair of the filename and its usage status
|
||||||
|
.map { isUsed -> Pair(filename, isUsed) }
|
||||||
|
}
|
||||||
|
// Handle any errors that occur during processing
|
||||||
|
.onErrorResumeNext { error: Throwable ->
|
||||||
|
// Log the error and continue with an empty Observable
|
||||||
|
Timber.e(error, "Error checking file usage batch")
|
||||||
|
Observable.empty()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the change is reviewable or not.
|
* Checks if the change is reviewable or not.
|
||||||
* - checks the type and revisionId of the change
|
* - checks the type and revisionId of the change
|
||||||
|
|
@ -145,5 +196,6 @@ class ReviewHelper
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val imageExtensions = arrayOf(".jpg", ".jpeg", ".png")
|
private val imageExtensions = arrayOf(".jpg", ".jpeg", ".png")
|
||||||
|
private const val MAX_RETRIES = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue