mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
OkHttpJsonApi#getMediaList migrated to retrofit (#3054)
* Migrated OkHttpJsonApi#getMediaList partially to MediaClient#getCategoryImages * Migrated rest of OkHttpJsonApi#getMediaList functionality to MediaClient#getCategoryImages * Removed unused code and tests * Fixed small bug * Added tests * Removed unused CategoryImageController
This commit is contained in:
parent
d5198be3e3
commit
97122296dd
9 changed files with 153 additions and 331 deletions
|
|
@ -1,30 +0,0 @@
|
||||||
package fr.free.nrw.commons.category;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
|
||||||
import io.reactivex.Single;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class CategoryImageController {
|
|
||||||
|
|
||||||
private OkHttpJsonApiClient okHttpJsonApiClient;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CategoryImageController(OkHttpJsonApiClient okHttpJsonApiClient) {
|
|
||||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a category name as input and calls the API to get a list of images for that category
|
|
||||||
* @param categoryName
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Single<List<Media>> getCategoryImages(String categoryName) {
|
|
||||||
return okHttpJsonApiClient.getMediaList("category", categoryName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,6 +27,7 @@ import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
|
@ -56,7 +57,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
private boolean isLoading = true;
|
private boolean isLoading = true;
|
||||||
private String categoryName = null;
|
private String categoryName = null;
|
||||||
|
|
||||||
@Inject CategoryImageController controller;
|
@Inject MediaClient mediaClient;
|
||||||
@Inject
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
JsonKvStore categoryKvStore;
|
JsonKvStore categoryKvStore;
|
||||||
|
|
@ -116,7 +117,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
progressBar.setVisibility(VISIBLE);
|
progressBar.setVisibility(VISIBLE);
|
||||||
compositeDisposable.add(controller.getCategoryImages(categoryName)
|
compositeDisposable.add(mediaClient.getMediaListFromCategory(categoryName)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
@ -222,7 +223,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
progressBar.setVisibility(VISIBLE);
|
||||||
compositeDisposable.add(controller.getCategoryImages(categoryName)
|
compositeDisposable.add(mediaClient.getMediaListFromCategory(categoryName)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@ public class NetworkingModule {
|
||||||
WIKIDATA_SPARQL_QUERY_URL,
|
WIKIDATA_SPARQL_QUERY_URL,
|
||||||
BuildConfig.WIKIMEDIA_CAMPAIGNS_URL,
|
BuildConfig.WIKIMEDIA_CAMPAIGNS_URL,
|
||||||
BuildConfig.WIKIMEDIA_API_HOST,
|
BuildConfig.WIKIMEDIA_API_HOST,
|
||||||
defaultKvStore,
|
|
||||||
gson);
|
gson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
|
@ -32,18 +31,11 @@ import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
|
|
@ -69,7 +61,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
@Inject RecentSearchesDao recentSearchesDao;
|
@Inject RecentSearchesDao recentSearchesDao;
|
||||||
@Inject
|
@Inject
|
||||||
OkHttpJsonApiClient okHttpJsonApiClient;
|
MediaClient mediaClient;
|
||||||
@Inject
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
JsonKvStore defaultKvStore;
|
JsonKvStore defaultKvStore;
|
||||||
|
|
@ -148,7 +140,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
bottomProgressBar.setVisibility(GONE);
|
bottomProgressBar.setVisibility(GONE);
|
||||||
queryList.clear();
|
queryList.clear();
|
||||||
imagesAdapter.clear();
|
imagesAdapter.clear();
|
||||||
compositeDisposable.add(okHttpJsonApiClient.getMediaList("search", query)
|
compositeDisposable.add(mediaClient.getMediaListFromSearch(query)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
@ -165,7 +157,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
bottomProgressBar.setVisibility(View.VISIBLE);
|
bottomProgressBar.setVisibility(View.VISIBLE);
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
compositeDisposable.add(okHttpJsonApiClient.getMediaList("search", query)
|
compositeDisposable.add(mediaClient.getMediaListFromSearch(query)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
@ -228,7 +220,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
private void initErrorView() {
|
private void initErrorView() {
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
imagesNotFoundView.setVisibility(VISIBLE);
|
imagesNotFoundView.setVisibility(VISIBLE);
|
||||||
imagesNotFoundView.setText(getString(R.string.images_not_found, query));
|
imagesNotFoundView.setText(getString(R.string.images_not_found));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
package fr.free.nrw.commons.media;
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -14,9 +24,13 @@ public class MediaClient {
|
||||||
|
|
||||||
private final MediaInterface mediaInterface;
|
private final MediaInterface mediaInterface;
|
||||||
|
|
||||||
|
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
||||||
|
private Map<String, Map<String, String>> continuationStore;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MediaClient(MediaInterface mediaInterface) {
|
public MediaClient(MediaInterface mediaInterface) {
|
||||||
this.mediaInterface = mediaInterface;
|
this.mediaInterface = mediaInterface;
|
||||||
|
this.continuationStore = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,4 +57,51 @@ public class MediaClient {
|
||||||
.query().allImages().size() > 0)
|
.query().allImages().size() > 0)
|
||||||
.singleOrError();
|
.singleOrError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes the category as input and returns a list of Media objects filtered using image generator query
|
||||||
|
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
||||||
|
*
|
||||||
|
* @param category the search category. Must start with "Category:"
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Single<List<Media>> getMediaListFromCategory(String category) {
|
||||||
|
return responseToMediaList(
|
||||||
|
continuationStore.containsKey("category_" + category) ?
|
||||||
|
mediaInterface.getMediaListFromCategory(category, 10, continuationStore.get("category_" + category)) : //if true
|
||||||
|
mediaInterface.getMediaListFromCategory(category, 10, Collections.emptyMap()),
|
||||||
|
"category_" + category); //if false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes a keyword as input and returns a list of Media objects filtered using image generator query
|
||||||
|
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
||||||
|
*
|
||||||
|
* @param keyword the search keyword
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Single<List<Media>> getMediaListFromSearch(String keyword) {
|
||||||
|
return responseToMediaList(
|
||||||
|
continuationStore.containsKey("search_" + keyword) ?
|
||||||
|
mediaInterface.getMediaListFromSearch(keyword, 10, continuationStore.get("search_" + keyword)) : //if true
|
||||||
|
mediaInterface.getMediaListFromSearch(keyword, 10, Collections.emptyMap()), //if false
|
||||||
|
"search_" + keyword);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Single<List<Media>> responseToMediaList(Observable<MwQueryResponse> response, String key) {
|
||||||
|
return response.flatMap(mwQueryResponse -> {
|
||||||
|
if (null == mwQueryResponse
|
||||||
|
|| null == mwQueryResponse.query()
|
||||||
|
|| null == mwQueryResponse.query().pages()) {
|
||||||
|
return Observable.empty();
|
||||||
|
}
|
||||||
|
continuationStore.put(key, mwQueryResponse.continuation());
|
||||||
|
return Observable.fromIterable(mwQueryResponse.query().pages());
|
||||||
|
})
|
||||||
|
.map(Media::from)
|
||||||
|
.collect(ArrayList<Media>::new, List::add);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package fr.free.nrw.commons.media;
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
import retrofit2.http.QueryMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for interacting with Commons media related APIs
|
* Interface for interacting with Commons media related APIs
|
||||||
|
|
@ -12,6 +16,7 @@ import retrofit2.http.Query;
|
||||||
public interface MediaInterface {
|
public interface MediaInterface {
|
||||||
/**
|
/**
|
||||||
* Checks if a page exists or not.
|
* Checks if a page exists or not.
|
||||||
|
*
|
||||||
* @param title the title of the page to be checked
|
* @param title the title of the page to be checked
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,9 +25,42 @@ public interface MediaInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if file exists
|
* Check if file exists
|
||||||
|
*
|
||||||
* @param aisha1 the SHA of the media file to be checked
|
* @param aisha1 the SHA of the media file to be checked
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@GET("w/api.php?action=query&format=json&formatversion=2&list=allimages")
|
@GET("w/api.php?action=query&format=json&formatversion=2&list=allimages")
|
||||||
Observable<MwQueryResponse> checkFileExistsUsingSha(@Query("aisha1") String aisha1);
|
Observable<MwQueryResponse> checkFileExistsUsingSha(@Query("aisha1") String aisha1);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves a list of Media objects filtered using image generator query
|
||||||
|
*
|
||||||
|
* @param category the category name. Must start with "Category:"
|
||||||
|
* @param itemLimit how many images are returned
|
||||||
|
* @param continuation the continuation string from the previous query or empty map
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||||
|
"&generator=categorymembers&gcmtype=file&gcmsort=timestamp&gcmdir=desc" + //Category parameters
|
||||||
|
"&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=640" + //Media property parameters
|
||||||
|
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
|
||||||
|
"|Artist|LicenseShortName|LicenseUrl")
|
||||||
|
Observable<MwQueryResponse> getMediaListFromCategory(@Query("gcmtitle") String category, @Query("gcmlimit") int itemLimit, @QueryMap Map<String, String> continuation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves a list of Media objects filtered using image generator query
|
||||||
|
*
|
||||||
|
* @param keyword the searched keyword
|
||||||
|
* @param itemLimit how many images are returned
|
||||||
|
* @param continuation the continuation string from the previous query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||||
|
"&generator=search&gsrwhat=text&gsrnamespace=6" + //Search parameters
|
||||||
|
"&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=640" + //Media property parameters
|
||||||
|
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
|
||||||
|
"|Artist|LicenseShortName|LicenseUrl")
|
||||||
|
Observable<MwQueryResponse> getMediaListFromSearch(@Query("gsrsearch") String keyword, @Query("gsrlimit") int itemLimit, @QueryMap Map<String, String> continuation);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,15 +46,11 @@ import timber.log.Timber;
|
||||||
public class OkHttpJsonApiClient {
|
public class OkHttpJsonApiClient {
|
||||||
private static final String THUMB_SIZE = "640";
|
private static final String THUMB_SIZE = "640";
|
||||||
|
|
||||||
public static final Type mapType = new TypeToken<Map<String, String>>() {
|
|
||||||
}.getType();
|
|
||||||
|
|
||||||
private final OkHttpClient okHttpClient;
|
private final OkHttpClient okHttpClient;
|
||||||
private final HttpUrl wikiMediaToolforgeUrl;
|
private final HttpUrl wikiMediaToolforgeUrl;
|
||||||
private final String sparqlQueryUrl;
|
private final String sparqlQueryUrl;
|
||||||
private final String campaignsUrl;
|
private final String campaignsUrl;
|
||||||
private final String commonsBaseUrl;
|
private final String commonsBaseUrl;
|
||||||
private final JsonKvStore defaultKvStore;
|
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -64,14 +60,12 @@ public class OkHttpJsonApiClient {
|
||||||
String sparqlQueryUrl,
|
String sparqlQueryUrl,
|
||||||
String campaignsUrl,
|
String campaignsUrl,
|
||||||
String commonsBaseUrl,
|
String commonsBaseUrl,
|
||||||
JsonKvStore defaultKvStore,
|
|
||||||
Gson gson) {
|
Gson gson) {
|
||||||
this.okHttpClient = okHttpClient;
|
this.okHttpClient = okHttpClient;
|
||||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||||
this.campaignsUrl = campaignsUrl;
|
this.campaignsUrl = campaignsUrl;
|
||||||
this.commonsBaseUrl = commonsBaseUrl;
|
this.commonsBaseUrl = commonsBaseUrl;
|
||||||
this.defaultKvStore = defaultKvStore;
|
|
||||||
this.gson = gson;
|
this.gson = gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,6 +278,8 @@ public class OkHttpJsonApiClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whenever imageInfo is fetched, these common properties can be specified for the API call
|
* Whenever imageInfo is fetched, these common properties can be specified for the API call
|
||||||
* https://www.mediawiki.org/wiki/API:Imageinfo
|
* https://www.mediawiki.org/wiki/API:Imageinfo
|
||||||
|
|
@ -304,124 +300,4 @@ public class OkHttpJsonApiClient {
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method takes the keyword and queryType as input and returns a list of Media objects filtered using image generator query
|
|
||||||
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
|
||||||
* @param queryType queryType can be "search" OR "category"
|
|
||||||
* @param keyword the search keyword. Can be either category name or search query
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public Single<List<Media>> getMediaList(String queryType, String keyword) {
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl
|
|
||||||
.parse(commonsBaseUrl)
|
|
||||||
.newBuilder()
|
|
||||||
.addQueryParameter("action", "query")
|
|
||||||
.addQueryParameter("format", "json")
|
|
||||||
.addQueryParameter("formatversion", "2");
|
|
||||||
|
|
||||||
|
|
||||||
if (queryType.equals("search")) {
|
|
||||||
appendSearchParam(keyword, urlBuilder);
|
|
||||||
} else {
|
|
||||||
appendCategoryParams(keyword, urlBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
appendQueryContinueValues(keyword, urlBuilder);
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(appendMediaProperties(urlBuilder).build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return Single.fromCallable(() -> {
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
List<Media> mediaList = new ArrayList<>();
|
|
||||||
if (response.body() != null && response.isSuccessful()) {
|
|
||||||
String json = response.body().string();
|
|
||||||
MwQueryResponse mwQueryResponse = gson.fromJson(json, MwQueryResponse.class);
|
|
||||||
if (null == mwQueryResponse
|
|
||||||
|| null == mwQueryResponse.query()
|
|
||||||
|| null == mwQueryResponse.query().pages()) {
|
|
||||||
return mediaList;
|
|
||||||
}
|
|
||||||
putContinueValues(keyword, mwQueryResponse.continuation());
|
|
||||||
|
|
||||||
List<MwQueryPage> pages = mwQueryResponse.query().pages();
|
|
||||||
for (MwQueryPage page : pages) {
|
|
||||||
Media media = Media.from(page);
|
|
||||||
if (media != null) {
|
|
||||||
mediaList.add(media);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mediaList;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append params for search query.
|
|
||||||
*
|
|
||||||
* @param query the search query to be sent to the API
|
|
||||||
* @param urlBuilder builder for HttpUrl
|
|
||||||
*/
|
|
||||||
private void appendSearchParam(String query, HttpUrl.Builder urlBuilder) {
|
|
||||||
urlBuilder.addQueryParameter("generator", "search")
|
|
||||||
.addQueryParameter("gsrwhat", "text")
|
|
||||||
.addQueryParameter("gsrnamespace", "6")
|
|
||||||
.addQueryParameter("gsrlimit", "25")
|
|
||||||
.addQueryParameter("gsrsearch", query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It takes a urlBuilder and appends all the continue values as query parameters
|
|
||||||
*
|
|
||||||
* @param query
|
|
||||||
* @param urlBuilder
|
|
||||||
*/
|
|
||||||
private void appendQueryContinueValues(String query, HttpUrl.Builder urlBuilder) {
|
|
||||||
Map<String, String> continueValues = getContinueValues(query);
|
|
||||||
if (continueValues != null && continueValues.size() > 0) {
|
|
||||||
for (Map.Entry<String, String> entry : continueValues.entrySet()) {
|
|
||||||
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append parameters for category image generator
|
|
||||||
*
|
|
||||||
* @param categoryName name of the category
|
|
||||||
* @param urlBuilder HttpUrl builder
|
|
||||||
*/
|
|
||||||
private void appendCategoryParams(String categoryName, HttpUrl.Builder urlBuilder) {
|
|
||||||
urlBuilder.addQueryParameter("generator", "categorymembers")
|
|
||||||
.addQueryParameter("gcmtype", "file")
|
|
||||||
.addQueryParameter("gcmtitle", categoryName)
|
|
||||||
.addQueryParameter("gcmsort", "timestamp")//property to sort by;timestamp
|
|
||||||
.addQueryParameter("gcmdir", "desc")//in which direction to sort;descending
|
|
||||||
.addQueryParameter("gcmlimit", "10");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the continue values for action=query
|
|
||||||
* These values are sent to the server in the subsequent call to fetch results after this point
|
|
||||||
*
|
|
||||||
* @param keyword
|
|
||||||
* @param values
|
|
||||||
*/
|
|
||||||
private void putContinueValues(String keyword, Map<String, String> values) {
|
|
||||||
defaultKvStore.putJson("query_continue_" + keyword, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a map of continue values from shared preferences.
|
|
||||||
* These values are appended to the next API call
|
|
||||||
*
|
|
||||||
* @param keyword
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Map<String, String> getContinueValues(String keyword) {
|
|
||||||
return defaultKvStore.getJson("query_continue_" + keyword, mapType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
package fr.free.nrw.commons.media
|
package fr.free.nrw.commons.media
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import junit.framework.Assert.assertFalse
|
import junit.framework.Assert.*
|
||||||
import junit.framework.Assert.assertTrue
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
import org.mockito.*
|
||||||
import org.mockito.InjectMocks
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
import org.wikipedia.dataclient.mwapi.ImageDetails
|
import org.wikipedia.dataclient.mwapi.ImageDetails
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||||
|
import org.wikipedia.gallery.ImageInfo
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import java.util.*
|
||||||
|
import org.mockito.Captor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MediaClientTest {
|
class MediaClientTest {
|
||||||
|
|
||||||
|
|
@ -97,4 +101,36 @@ class MediaClientTest {
|
||||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||||
assertFalse(checkFileExistsUsingSha)
|
assertFalse(checkFileExistsUsingSha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private val continuationCaptor: ArgumentCaptor<Map<String, String>>? = null
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getMediaListFromCategoryTwice() {
|
||||||
|
val mockContinuation= mapOf(Pair("gcmcontinue", "test"))
|
||||||
|
val imageInfo = ImageInfo()
|
||||||
|
|
||||||
|
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||||
|
`when`(mwQueryPage.title()).thenReturn("Test")
|
||||||
|
`when`(mwQueryPage.imageInfo()).thenReturn(imageInfo)
|
||||||
|
|
||||||
|
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||||
|
`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
||||||
|
|
||||||
|
val mockResponse = mock(MwQueryResponse::class.java)
|
||||||
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
`when`(mockResponse.continuation()).thenReturn(mockContinuation)
|
||||||
|
|
||||||
|
`when`(mediaInterface!!.getMediaListFromCategory(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(),
|
||||||
|
continuationCaptor!!.capture()))
|
||||||
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
val media1 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||||
|
val media2 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||||
|
|
||||||
|
assertEquals(continuationCaptor.allValues[0], emptyMap<String, String>())
|
||||||
|
assertEquals(continuationCaptor.allValues[1], mockContinuation)
|
||||||
|
|
||||||
|
assertEquals(media1.filename, "Test")
|
||||||
|
assertEquals(media2.filename, "Test")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,7 @@ import com.google.gson.Gson
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
import fr.free.nrw.commons.TestCommonsApplication
|
import fr.free.nrw.commons.TestCommonsApplication
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient.mapType
|
|
||||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||||
import junit.framework.Assert.assertEquals
|
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
|
@ -18,7 +16,6 @@ import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
@ -55,7 +52,7 @@ class OkHttpJsonApiClientTest {
|
||||||
val sparqlUrl = "http://" + sparqlServer.hostName + ":" + sparqlServer.port + "/"
|
val sparqlUrl = "http://" + sparqlServer.hostName + ":" + sparqlServer.port + "/"
|
||||||
val campaignsUrl = "http://" + campaignsServer.hostName + ":" + campaignsServer.port + "/"
|
val campaignsUrl = "http://" + campaignsServer.hostName + ":" + campaignsServer.port + "/"
|
||||||
val serverUrl = "http://" + server.hostName + ":" + server.port + "/"
|
val serverUrl = "http://" + server.hostName + ":" + server.port + "/"
|
||||||
testObject = OkHttpJsonApiClient(okHttpClient, HttpUrl.get(toolsForgeUrl), sparqlUrl, campaignsUrl, serverUrl, sharedPreferences, Gson())
|
testObject = OkHttpJsonApiClient(okHttpClient, HttpUrl.get(toolsForgeUrl), sparqlUrl, campaignsUrl, serverUrl, Gson())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,96 +66,6 @@ class OkHttpJsonApiClientTest {
|
||||||
campaignsServer.shutdown()
|
campaignsServer.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test response for category images
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun getCategoryImages() {
|
|
||||||
server.enqueue(getFirstPageOfImages())
|
|
||||||
testFirstPageQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* test paginated response for category images
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun getCategoryImagesWithContinue() {
|
|
||||||
server.enqueue(getFirstPageOfImages())
|
|
||||||
server.enqueue(getSecondPageOfImages())
|
|
||||||
testFirstPageQuery()
|
|
||||||
|
|
||||||
`when`(sharedPreferences.getJson<HashMap<String, String>>("query_continue_Watercraft moored off shore", mapType))
|
|
||||||
.thenReturn(hashMapOf(Pair("gcmcontinue", "testvalue"), Pair("continue", "gcmcontinue||")))
|
|
||||||
|
|
||||||
|
|
||||||
val categoryImagesContinued = testObject.getMediaList("category", "Watercraft moored off shore")!!.blockingGet()
|
|
||||||
|
|
||||||
assertBasicRequestParameters(server, "GET").let { request ->
|
|
||||||
parseQueryParams(request).let { body ->
|
|
||||||
Assert.assertEquals("json", body["format"])
|
|
||||||
Assert.assertEquals("2", body["formatversion"])
|
|
||||||
Assert.assertEquals("query", body["action"])
|
|
||||||
Assert.assertEquals("categorymembers", body["generator"])
|
|
||||||
Assert.assertEquals("file", body["gcmtype"])
|
|
||||||
Assert.assertEquals("Watercraft moored off shore", body["gcmtitle"])
|
|
||||||
Assert.assertEquals("timestamp", body["gcmsort"])
|
|
||||||
Assert.assertEquals("desc", body["gcmdir"])
|
|
||||||
Assert.assertEquals("testvalue", body["gcmcontinue"])
|
|
||||||
Assert.assertEquals("gcmcontinue||", body["continue"])
|
|
||||||
Assert.assertEquals("imageinfo", body["prop"])
|
|
||||||
Assert.assertEquals("url|extmetadata", body["iiprop"])
|
|
||||||
Assert.assertEquals("DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl", body["iiextmetadatafilter"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(categoryImagesContinued.size, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test response for search images
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun getSearchImages() {
|
|
||||||
server.enqueue(getFirstPageOfImages())
|
|
||||||
testFirstPageSearchQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test response for paginated search
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun getSearchImagesWithContinue() {
|
|
||||||
server.enqueue(getFirstPageOfSearchImages())
|
|
||||||
server.enqueue(getSecondPageOfSearchImages())
|
|
||||||
testFirstPageSearchQuery()
|
|
||||||
|
|
||||||
`when`(sharedPreferences.getJson<HashMap<String, String>>("query_continue_Watercraft moored off shore", mapType))
|
|
||||||
.thenReturn(hashMapOf(Pair("gsroffset", "25"), Pair("continue", "gsroffset||")))
|
|
||||||
|
|
||||||
|
|
||||||
val categoryImagesContinued = testObject.getMediaList("search", "Watercraft moored off shore")!!.blockingGet()
|
|
||||||
|
|
||||||
assertBasicRequestParameters(server, "GET").let { request ->
|
|
||||||
parseQueryParams(request).let { body ->
|
|
||||||
Assert.assertEquals("json", body["format"])
|
|
||||||
Assert.assertEquals("2", body["formatversion"])
|
|
||||||
Assert.assertEquals("query", body["action"])
|
|
||||||
Assert.assertEquals("search", body["generator"])
|
|
||||||
Assert.assertEquals("text", body["gsrwhat"])
|
|
||||||
Assert.assertEquals("6", body["gsrnamespace"])
|
|
||||||
Assert.assertEquals("25", body["gsrlimit"])
|
|
||||||
Assert.assertEquals("Watercraft moored off shore", body["gsrsearch"])
|
|
||||||
Assert.assertEquals("25", body["gsroffset"])
|
|
||||||
Assert.assertEquals("gsroffset||", body["continue"])
|
|
||||||
Assert.assertEquals("imageinfo", body["prop"])
|
|
||||||
Assert.assertEquals("url|extmetadata", body["iiprop"])
|
|
||||||
Assert.assertEquals("DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl", body["iiextmetadatafilter"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(categoryImagesContinued.size, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test response for getting media without generator
|
* Test response for getting media without generator
|
||||||
*/
|
*/
|
||||||
|
|
@ -236,64 +143,6 @@ class OkHttpJsonApiClientTest {
|
||||||
assert(media is Media)
|
assert(media is Media)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testFirstPageSearchQuery() {
|
|
||||||
val categoryImages = testObject.getMediaList("search", "Watercraft moored off shore")!!.blockingGet()
|
|
||||||
|
|
||||||
assertBasicRequestParameters(server, "GET").let { request ->
|
|
||||||
parseQueryParams(request).let { body ->
|
|
||||||
Assert.assertEquals("json", body["format"])
|
|
||||||
Assert.assertEquals("2", body["formatversion"])
|
|
||||||
Assert.assertEquals("query", body["action"])
|
|
||||||
Assert.assertEquals("search", body["generator"])
|
|
||||||
Assert.assertEquals("text", body["gsrwhat"])
|
|
||||||
Assert.assertEquals("6", body["gsrnamespace"])
|
|
||||||
Assert.assertEquals("25", body["gsrlimit"])
|
|
||||||
Assert.assertEquals("Watercraft moored off shore", body["gsrsearch"])
|
|
||||||
Assert.assertEquals("imageinfo", body["prop"])
|
|
||||||
Assert.assertEquals("url|extmetadata", body["iiprop"])
|
|
||||||
Assert.assertEquals("DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl", body["iiextmetadatafilter"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertEquals(categoryImages.size, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun testFirstPageQuery() {
|
|
||||||
val categoryImages = testObject.getMediaList("category", "Watercraft moored off shore")?.blockingGet()
|
|
||||||
|
|
||||||
assertBasicRequestParameters(server, "GET").let { request ->
|
|
||||||
parseQueryParams(request).let { body ->
|
|
||||||
Assert.assertEquals("json", body["format"])
|
|
||||||
Assert.assertEquals("2", body["formatversion"])
|
|
||||||
Assert.assertEquals("query", body["action"])
|
|
||||||
Assert.assertEquals("categorymembers", body["generator"])
|
|
||||||
Assert.assertEquals("file", body["gcmtype"])
|
|
||||||
Assert.assertEquals("Watercraft moored off shore", body["gcmtitle"])
|
|
||||||
Assert.assertEquals("timestamp", body["gcmsort"])
|
|
||||||
Assert.assertEquals("desc", body["gcmdir"])
|
|
||||||
Assert.assertEquals("imageinfo", body["prop"])
|
|
||||||
Assert.assertEquals("url|extmetadata", body["iiprop"])
|
|
||||||
Assert.assertEquals("DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl", body["iiextmetadatafilter"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertEquals(categoryImages?.size, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFirstPageOfImages(): MockResponse {
|
|
||||||
return getMediaList("gcmcontinue", "testvalue", "gcmcontinue||", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSecondPageOfImages(): MockResponse {
|
|
||||||
return getMediaList("gcmcontinue", "testvalue2", "gcmcontinue||", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFirstPageOfSearchImages(): MockResponse {
|
|
||||||
return getMediaList("gsroffset", "25", "gsroffset||", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSecondPageOfSearchImages(): MockResponse {
|
|
||||||
return getMediaList("gsroffset", "25", "gsroffset||", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a MockResponse object which contains a list of media pages
|
* Generate a MockResponse object which contains a list of media pages
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue