Skip to content

Commit 549d943

Browse files
authored
Merge pull request #7 from amyWenhui/times-called
javadoc
2 parents e617507 + 617957e commit 549d943

File tree

2 files changed

+179
-19
lines changed

2 files changed

+179
-19
lines changed

app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java

+160-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import android.view.MenuItem;
1313
import android.view.MotionEvent;
1414
import android.view.View;
15+
import androidx.core.util.Pair;
1516
import androidx.fragment.app.Fragment;
1617
import androidx.fragment.app.FragmentManager;
1718
import fr.free.nrw.commons.Media;
@@ -62,9 +63,16 @@ public class ReviewActivity extends BaseActivity {
6263

6364
private List<Media> cachedMedia = new ArrayList<>();
6465

66+
/** Constants for managing media cache in the review activity */
67+
// Name of SharedPreferences file for storing review activity preferences
6568
private static final String PREF_NAME = "ReviewActivityPrefs";
69+
// Key for storing the timestamp of last cache update
6670
private static final String LAST_CACHE_TIME_KEY = "lastCacheTime";
71+
72+
// Maximum number of media files to store in cache
6773
private static final int CACHE_SIZE = 5;
74+
// Cache expiration time in milliseconds (24 hours)
75+
6876
private static final long CACHE_EXPIRY_TIME = 24 * 60 * 60 * 1000;
6977

7078
@Override
@@ -113,10 +121,21 @@ protected void onCreate(Bundle savedInstanceState) {
113121
Drawable d[]=binding.skipImage.getCompoundDrawablesRelative();
114122
d[2].setColorFilter(getApplicationContext().getResources().getColor(R.color.button_blue), PorterDuff.Mode.SRC_IN);
115123

124+
/**
125+
* Restores the previous state of the activity or initializes a new review session.
126+
* Checks if there's a saved media state from a previous session (e.g., before screen rotation).
127+
* If found, restores the last viewed image and its detail view.
128+
* Otherwise, starts a new random image review session.
129+
*
130+
* @param savedInstanceState Bundle containing the activity's previously saved state, if any
131+
*/
116132
if (savedInstanceState != null && savedInstanceState.getParcelable(SAVED_MEDIA) != null) {
133+
// Restore the previously viewed image if state exists
117134
updateImage(savedInstanceState.getParcelable(SAVED_MEDIA));
135+
// Restore media detail view (handles configuration changes like screen rotation)
118136
setUpMediaDetailOnOrientation();
119137
} else {
138+
// Start fresh review session with a random image
120139
runRandomizer();
121140
}
122141

@@ -145,54 +164,173 @@ public boolean onSupportNavigateUp() {
145164
return true;
146165
}
147166

167+
/**
168+
* Initiates the process of loading a random media file for review.
169+
* This method:
170+
* - Resets the UI state
171+
* - Shows loading indicator
172+
* - Manages media cache
173+
* - Either loads from cache or fetches new media
174+
*
175+
* The method is annotated with @SuppressLint("CheckResult") as the Observable
176+
* subscription is handled through CompositeDisposable in the implementation.
177+
*
178+
* @return true indicating successful initiation of the randomization process
179+
*/
148180
@SuppressLint("CheckResult")
149181
public boolean runRandomizer() {
182+
// Reset flag for tracking presence of non-hidden categories
150183
hasNonHiddenCategories = false;
184+
// Display loading indicator while fetching media
151185
binding.pbReviewImage.setVisibility(View.VISIBLE);
186+
// Reset view pager to first page
152187
binding.viewPagerReview.setCurrentItem(0);
153188

189+
// Check cache status and determine source of next media
154190
if (cachedMedia.isEmpty() || isCacheExpired()) {
191+
// Fetch and cache new media if cache is empty or expired
155192
fetchAndCacheMedia();
156193
} else {
194+
// Use next media file from existing cache
157195
processNextCachedMedia();
158196
}
159197
return true;
160198
}
161199

200+
/**
201+
* Batch checks whether multiple files from the cache are used in wikis.
202+
* This is a more efficient way to process multiple files compared to checking them one by one.
203+
*
204+
* @param mediaList List of Media objects to check for usage
205+
*/
206+
/**
207+
* Batch checks whether multiple files from the cache are used in wikis.
208+
* This is a more efficient way to process multiple files compared to checking them one by one.
209+
*
210+
* @param mediaList List of Media objects to check for usage
211+
*/
212+
private void batchCheckFilesUsage(List<Media> mediaList) {
213+
// Extract filenames from media objects
214+
List<String> filenames = new ArrayList<>();
215+
for (Media media : mediaList) {
216+
if (media.getFilename() != null) {
217+
filenames.add(media.getFilename());
218+
}
219+
}
220+
221+
compositeDisposable.add(
222+
reviewHelper.checkFileUsageBatch(filenames)
223+
.subscribeOn(Schedulers.io())
224+
.observeOn(AndroidSchedulers.mainThread())
225+
.toList()
226+
.subscribe(results -> {
227+
// Process each result
228+
for (kotlin.Pair<String, Boolean> result : results) {
229+
String filename = result.getFirst();
230+
Boolean isUsed = result.getSecond();
231+
232+
// Find corresponding media object
233+
for (Media media : mediaList) {
234+
if (filename.equals(media.getFilename())) {
235+
if (!isUsed) {
236+
// If file is not used, proceed with category check
237+
findNonHiddenCategories(media);
238+
}
239+
break;
240+
}
241+
}
242+
}
243+
}, this::handleError));
244+
}
245+
246+
247+
/**
248+
* Fetches and caches new media files for review.
249+
* Uses RxJava to:
250+
* - Generate a range of indices for the desired cache size
251+
* - Fetch random media files asynchronously
252+
* - Handle thread scheduling between IO and UI operations
253+
* - Store the fetched media in cache
254+
*
255+
* The operation is added to compositeDisposable for proper lifecycle management.
256+
*/
162257
private void fetchAndCacheMedia() {
163-
compositeDisposable.add(Observable.range(0, CACHE_SIZE)
164-
.flatMap(i -> reviewHelper.getRandomMedia().toObservable())
165-
.subscribeOn(Schedulers.io())
166-
.observeOn(AndroidSchedulers.mainThread())
167-
.toList()
168-
.subscribe(mediaList -> {
169-
cachedMedia.clear();
170-
cachedMedia.addAll(mediaList);
171-
updateLastCacheTime();
172-
processNextCachedMedia();
173-
}, this::handleError));
258+
compositeDisposable.add(
259+
Observable.range(0, CACHE_SIZE)
260+
.flatMap(i -> reviewHelper.getRandomMedia().toObservable())
261+
.subscribeOn(Schedulers.io())
262+
.observeOn(AndroidSchedulers.mainThread())
263+
.toList()
264+
.subscribe(mediaList -> {
265+
// Clear existing cache
266+
cachedMedia.clear();
267+
268+
// Start batch check process
269+
batchCheckFilesUsage(mediaList);
270+
271+
// Update cache with new media
272+
cachedMedia.addAll(mediaList);
273+
updateLastCacheTime();
274+
275+
// Process first media item if available
276+
if (!cachedMedia.isEmpty()) {
277+
processNextCachedMedia();
278+
}
279+
}, this::handleError));
174280
}
175281

282+
/**
283+
* Processes the next media file from the cache.
284+
* If cache is not empty, removes and processes the first media file.
285+
* If cache is empty, triggers a new fetch operation.
286+
*
287+
* This method ensures continuous flow of media files for review
288+
* while maintaining the cache mechanism.
289+
*/
176290
private void processNextCachedMedia() {
177291
if (!cachedMedia.isEmpty()) {
292+
// Remove and get the first media from cache
178293
Media media = cachedMedia.remove(0);
294+
179295
checkWhetherFileIsUsedInWikis(media);
180296
} else {
297+
// Refill cache if empty
181298
fetchAndCacheMedia();
182299
}
183300
}
184301

302+
/**
303+
* Checks if the current cache has expired.
304+
* Cache expiration is determined by comparing the last cache time
305+
* with the current time against the configured expiry duration.
306+
*
307+
* @return true if cache has expired, false otherwise
308+
*/
185309
private boolean isCacheExpired() {
310+
// Get shared preferences instance
186311
SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
312+
187313
long lastCacheTime = prefs.getLong(LAST_CACHE_TIME_KEY, 0);
314+
188315
long currentTime = System.currentTimeMillis();
316+
189317
return (currentTime - lastCacheTime) > CACHE_EXPIRY_TIME;
190318
}
191319

320+
321+
/**
322+
* Updates the timestamp of the last cache operation.
323+
* Stores the current time in SharedPreferences to track
324+
* cache freshness for future operations.
325+
*/
192326
private void updateLastCacheTime() {
327+
193328
SharedPreferences prefs = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
329+
194330
SharedPreferences.Editor editor = prefs.edit();
331+
// Store current timestamp as last cache time
195332
editor.putLong(LAST_CACHE_TIME_KEY, System.currentTimeMillis());
333+
// Apply changes asynchronously
196334
editor.apply();
197335
}
198336

@@ -305,10 +443,20 @@ public void showReviewImageInfo() {
305443
null,
306444
null);
307445
}
446+
/**
447+
* Handles errors that occur during media processing operations.
448+
* This is a generic error handler that:
449+
* - Hides the loading indicator
450+
* - Shows a user-friendly error message via Snackbar
451+
*
452+
* Used as error callback for RxJava operations and other async tasks.
453+
*
454+
* @param error The Throwable that was caught during operation
455+
*/
308456
private void handleError(Throwable error) {
309457
binding.pbReviewImage.setVisibility(View.GONE);
458+
// Show error message to user via Snackbar
310459
ViewUtil.showShortSnackbar(binding.drawerLayout, R.string.error_review);
311-
312460
}
313461

314462

app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt

+19-7
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@ class ReviewHelper
2626

2727

2828
/**
29-
* Gets multiple random media items for review.
30-
* - Fetches recent changes and filters them
31-
* - Checks if files are nominated for deletion
32-
* - Filters out already reviewed images
29+
* Fetches recent changes from MediaWiki API
30+
* Calls the API to get the latest 50 changes
31+
* When more results are available, the query gets continued beyond this range
3332
*
34-
* @param count Number of media items to fetch
35-
* @return Observable of Media items
33+
* @return
3634
*/
3735
private fun getRecentChanges() =
3836
reviewInterface
@@ -135,14 +133,28 @@ class ReviewHelper
135133

136134

137135

138-
136+
/**
137+
* Batch checks whether multiple files are being used in any wiki pages.
138+
* This method processes a list of filenames in parallel using RxJava Observables.
139+
*
140+
* @param filenames A list of filenames to check for usage
141+
* @return Observable emitting pairs of filename and usage status:
142+
* - The String represents the filename
143+
* - The Boolean indicates whether the file is used (true) or not (false)
144+
* If an error occurs during processing, it will log the error and emit an empty Observable
145+
*/
139146
fun checkFileUsageBatch(filenames: List<String>): Observable<Pair<String, Boolean>> =
147+
// Convert the list of filenames into an Observable stream
140148
Observable.fromIterable(filenames)
149+
// For each filename, check its usage and pair it with the result
141150
.flatMap { filename ->
142151
checkFileUsage(filename)
152+
// Create a pair of the filename and its usage status
143153
.map { isUsed -> Pair(filename, isUsed) }
144154
}
155+
// Handle any errors that occur during processing
145156
.onErrorResumeNext { error: Throwable ->
157+
// Log the error and continue with an empty Observable
146158
Timber.e(error, "Error checking file usage batch")
147159
Observable.empty()
148160
}

0 commit comments

Comments
 (0)