Convert OkHttpJsonApiClient and CategoryApi to kotlin

This commit is contained in:
Paul Hawke 2024-11-30 20:38:05 -06:00
parent 33548fa57d
commit 79de03964e
7 changed files with 633 additions and 783 deletions

View file

@ -52,12 +52,12 @@ class CampaignsPresenter @Inject constructor(
return
}
okHttpJsonApiClient.campaigns
okHttpJsonApiClient.getCampaigns()
.observeOn(mainThreadScheduler)
.subscribeOn(ioScheduler)
.doOnSubscribe { disposable = it }
.subscribe({ campaignResponseDTO ->
val campaigns = campaignResponseDTO.campaigns?.toMutableList()
val campaigns = campaignResponseDTO?.campaigns?.toMutableList()
if (campaigns.isNullOrEmpty()) {
Timber.e("The campaigns list is empty")
view!!.showCampaigns(null)

View file

@ -1,99 +0,0 @@
package fr.free.nrw.commons.mwapi;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX;
import com.google.gson.Gson;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage;
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse;
import io.reactivex.Single;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import timber.log.Timber;
/**
* Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates
* with nearby Commons categories. Parses the results using GSON to obtain a list of relevant
* categories. Note: that caller is responsible for executing the request() method on a background
* thread.
*/
public class CategoryApi {
private final OkHttpClient okHttpClient;
private final Gson gson;
@Inject
public CategoryApi(final OkHttpClient okHttpClient, final Gson gson) {
this.okHttpClient = okHttpClient;
this.gson = gson;
}
public Single<List<CategoryItem>> request(String coords) {
return Single.fromCallable(() -> {
HttpUrl apiUrl = buildUrl(coords);
Timber.d("URL: %s", apiUrl.toString());
Request request = new Request.Builder().get().url(apiUrl).build();
Response response = okHttpClient.newCall(request).execute();
ResponseBody body = response.body();
if (body == null) {
return Collections.emptyList();
}
MwQueryResponse apiResponse = gson.fromJson(body.charStream(), MwQueryResponse.class);
Set<CategoryItem> categories = new LinkedHashSet<>();
if (apiResponse != null && apiResponse.query() != null && apiResponse.query().pages() != null) {
for (MwQueryPage page : apiResponse.query().pages()) {
if (page.categories() != null) {
for (MwQueryPage.Category category : page.categories()) {
categories.add(new CategoryItem(category.title().replace(CATEGORY_PREFIX, ""), "", "", false));
}
}
}
}
return new ArrayList<>(categories);
});
}
/**
* Builds URL with image coords for MediaWiki API calls
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
*
* @param coords Coordinates to build query with
* @return URL for API query
*/
private HttpUrl buildUrl(final String coords) {
return HttpUrl
.parse(BuildConfig.WIKIMEDIA_API_HOST)
.newBuilder()
.addQueryParameter("action", "query")
.addQueryParameter("prop", "categories|coordinates|pageprops")
.addQueryParameter("format", "json")
.addQueryParameter("clshow", "!hidden")
.addQueryParameter("coprop", "type|name|dim|country|region|globe")
.addQueryParameter("codistancefrompoint", coords)
.addQueryParameter("generator", "geosearch")
.addQueryParameter("ggscoord", coords)
.addQueryParameter("ggsradius", "10000")
.addQueryParameter("ggslimit", "10")
.addQueryParameter("ggsnamespace", "6")
.addQueryParameter("ggsprop", "type|name|dim|country|region|globe")
.addQueryParameter("ggsprimary", "all")
.addQueryParameter("formatversion", "2")
.build();
}
}

View file

@ -0,0 +1,83 @@
package fr.free.nrw.commons.mwapi
import com.google.gson.Gson
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.category.CATEGORY_PREFIX
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Single
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import timber.log.Timber
import javax.inject.Inject
/**
* Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates
* with nearby Commons categories. Parses the results using GSON to obtain a list of relevant
* categories. Note: that caller is responsible for executing the request() method on a background
* thread.
*/
class CategoryApi @Inject constructor(
private val okHttpClient: OkHttpClient,
private val gson: Gson
) {
private val apiUrl : HttpUrl by lazy { BuildConfig.WIKIMEDIA_API_HOST.toHttpUrlOrNull()!! }
fun request(coords: String): Single<List<CategoryItem>> = Single.fromCallable {
val apiUrl = buildUrl(coords)
Timber.d("URL: %s", apiUrl.toString())
val request: Request = Request.Builder().get().url(apiUrl).build()
val response = okHttpClient.newCall(request).execute()
val body = response.body ?: return@fromCallable emptyList<CategoryItem>()
val apiResponse = gson.fromJson(body.charStream(), MwQueryResponse::class.java)
val categories: MutableSet<CategoryItem> = mutableSetOf()
if (apiResponse?.query() != null && apiResponse.query()!!.pages() != null) {
for (page in apiResponse.query()!!.pages()!!) {
if (page.categories() != null) {
for (category in page.categories()!!) {
categories.add(
CategoryItem(
name = category.title().replace(CATEGORY_PREFIX, ""),
description = "",
thumbnail = "",
isSelected = false
)
)
}
}
}
}
ArrayList<CategoryItem>(categories)
}
/**
* Builds URL with image coords for MediaWiki API calls
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
*
* @param coords Coordinates to build query with
* @return URL for API query
*/
private fun buildUrl(coords: String): HttpUrl = apiUrl.newBuilder()
.addQueryParameter("action", "query")
.addQueryParameter("prop", "categories|coordinates|pageprops")
.addQueryParameter("format", "json")
.addQueryParameter("clshow", "!hidden")
.addQueryParameter("coprop", "type|name|dim|country|region|globe")
.addQueryParameter("codistancefrompoint", coords)
.addQueryParameter("generator", "geosearch")
.addQueryParameter("ggscoord", coords)
.addQueryParameter("ggsradius", "10000")
.addQueryParameter("ggslimit", "10")
.addQueryParameter("ggsnamespace", "6")
.addQueryParameter("ggsprop", "type|name|dim|country|region|globe")
.addQueryParameter("ggsprimary", "all")
.addQueryParameter("formatversion", "2")
.build()
}

View file

@ -1,677 +0,0 @@
package fr.free.nrw.commons.mwapi;
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LEADERBOARD_END_POINT;
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.UPDATE_AVATAR_END_POINT;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.model.ItemsClass;
import fr.free.nrw.commons.nearby.model.NearbyResponse;
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
import fr.free.nrw.commons.nearby.model.PlaceBindings;
import fr.free.nrw.commons.profile.achievements.FeaturedImages;
import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse;
import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.jetbrains.annotations.NotNull;
import timber.log.Timber;
/**
* Test methods in ok http api client
*/
@Singleton
public class OkHttpJsonApiClient {
private final OkHttpClient okHttpClient;
private final DepictsClient depictsClient;
private final HttpUrl wikiMediaToolforgeUrl;
private final String sparqlQueryUrl;
private final String campaignsUrl;
private final Gson gson;
@Inject
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
DepictsClient depictsClient,
HttpUrl wikiMediaToolforgeUrl,
String sparqlQueryUrl,
String campaignsUrl,
Gson gson) {
this.okHttpClient = okHttpClient;
this.depictsClient = depictsClient;
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
this.sparqlQueryUrl = sparqlQueryUrl;
this.campaignsUrl = campaignsUrl;
this.gson = gson;
}
/**
* The method will gradually calls the leaderboard API and fetches the leaderboard
*
* @param userName username of leaderboard user
* @param duration duration for leaderboard
* @param category category for leaderboard
* @param limit page size limit for list
* @param offset offset for the list
* @return LeaderboardResponse object
*/
@NonNull
public Observable<LeaderboardResponse> getLeaderboard(String userName, String duration,
String category, String limit, String offset) {
final String fetchLeaderboardUrlTemplate = wikiMediaToolforgeUrl
+ LEADERBOARD_END_POINT;
String url = String.format(Locale.ENGLISH,
fetchLeaderboardUrlTemplate,
userName,
duration,
category,
limit,
offset);
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
urlBuilder.addQueryParameter("user", userName);
urlBuilder.addQueryParameter("duration", duration);
urlBuilder.addQueryParameter("category", category);
urlBuilder.addQueryParameter("limit", limit);
urlBuilder.addQueryParameter("offset", offset);
Timber.i("Url %s", urlBuilder.toString());
Request request = new Request.Builder()
.url(urlBuilder.toString())
.build();
return Observable.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.body() != null && response.isSuccessful()) {
String json = response.body().string();
if (json == null) {
return new LeaderboardResponse();
}
Timber.d("Response for leaderboard is %s", json);
try {
return gson.fromJson(json, LeaderboardResponse.class);
} catch (Exception e) {
return new LeaderboardResponse();
}
}
return new LeaderboardResponse();
});
}
/**
* This method will update the leaderboard user avatar
*
* @param username username to update
* @param avatar url of the new avatar
* @return UpdateAvatarResponse object
*/
@NonNull
public Single<UpdateAvatarResponse> setAvatar(String username, String avatar) {
final String urlTemplate = wikiMediaToolforgeUrl
+ UPDATE_AVATAR_END_POINT;
return Single.fromCallable(() -> {
String url = String.format(Locale.ENGLISH,
urlTemplate,
username,
avatar);
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
urlBuilder.addQueryParameter("user", username);
urlBuilder.addQueryParameter("avatar", avatar);
Timber.i("Url %s", urlBuilder.toString());
Request request = new Request.Builder()
.url(urlBuilder.toString())
.build();
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.body() != null && response.isSuccessful()) {
String json = response.body().string();
if (json == null) {
return null;
}
try {
return gson.fromJson(json, UpdateAvatarResponse.class);
} catch (Exception e) {
return new UpdateAvatarResponse();
}
}
return null;
});
}
@NonNull
public Single<Integer> getUploadCount(String userName) {
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
urlBuilder
.addPathSegments("uploadsbyuser.py")
.addQueryParameter("user", userName);
if (ConfigUtils.isBetaFlavour()) {
urlBuilder.addQueryParameter("labs", "commonswiki");
}
Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
return Single.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.isSuccessful()) {
ResponseBody responseBody = response.body();
if (null != responseBody) {
String responseBodyString = responseBody.string().trim();
if (!TextUtils.isEmpty(responseBodyString)) {
try {
return Integer.parseInt(responseBodyString);
} catch (NumberFormatException e) {
Timber.e(e);
}
}
}
}
return 0;
});
}
@NonNull
public Single<Integer> getWikidataEdits(String userName) {
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
urlBuilder
.addPathSegments("wikidataedits.py")
.addQueryParameter("user", userName);
if (ConfigUtils.isBetaFlavour()) {
urlBuilder.addQueryParameter("labs", "commonswiki");
}
Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
return Single.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response != null &&
response.isSuccessful() && response.body() != null) {
String json = response.body().string();
if (json == null) {
return 0;
}
// Extract JSON from response
json = json.substring(json.indexOf('{'));
GetWikidataEditCountResponse countResponse = gson
.fromJson(json, GetWikidataEditCountResponse.class);
if (null != countResponse) {
return countResponse.getWikidataEditCount();
}
}
return 0;
});
}
/**
* This takes userName as input, which is then used to fetch the feedback/achievements
* statistics using OkHttp and JavaRx. This function return JSONObject
*
* @param userName MediaWiki user name
* @return
*/
public Single<FeedbackResponse> getAchievements(String userName) {
final String fetchAchievementUrlTemplate =
wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki"
: "/feedback.py");
return Single.fromCallable(() -> {
String url = String.format(
Locale.ENGLISH,
fetchAchievementUrlTemplate,
userName);
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
urlBuilder.addQueryParameter("user", userName);
Request request = new Request.Builder()
.url(urlBuilder.toString())
.build();
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.body() != null && response.isSuccessful()) {
String json = response.body().string();
if (json == null) {
return null;
}
// Extract JSON from response
json = json.substring(json.indexOf('{'));
Timber.d("Response for achievements is %s", json);
try {
return gson.fromJson(json, FeedbackResponse.class);
} catch (Exception e) {
return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
}
}
return null;
});
}
/**
* Make API Call to get Nearby Places
*
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @return
* @throws Exception
*/
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final String customQuery)
throws Exception {
Timber.d("Fetching nearby items at radius %s", radius);
Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null));
final String wikidataQuery;
if (customQuery != null) {
wikidataQuery = customQuery;
} else {
wikidataQuery = FileUtils.readFromResource(
"/queries/radius_query_for_upload_wizard.rq");
}
final String query = wikidataQuery
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
.replace("${LANG}", language);
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
final Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
final String json = response.body().string();
final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
final List<Place> places = new ArrayList<>();
for (final NearbyResultItem item : bindings) {
final Place placeFromNearbyItem = Place.from(item);
placeFromNearbyItem.setMonument(false);
places.add(placeFromNearbyItem);
}
return places;
}
throw new Exception(response.message());
}
/**
* Retrieves nearby places based on screen coordinates and optional query parameters.
*
* @param screenTopRight The top right corner of the screen (latitude, longitude).
* @param screenBottomLeft The bottom left corner of the screen (latitude, longitude).
* @param language The language for the query.
* @param shouldQueryForMonuments Flag indicating whether to include monuments in the query.
* @param customQuery Optional custom SPARQL query to use instead of default
* queries.
* @return A list of nearby places.
* @throws Exception If an error occurs during the retrieval process.
*/
@Nullable
public List<Place> getNearbyPlaces(
final fr.free.nrw.commons.location.LatLng screenTopRight,
final fr.free.nrw.commons.location.LatLng screenBottomLeft, final String language,
final boolean shouldQueryForMonuments, final String customQuery)
throws Exception {
Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null));
final String wikidataQuery;
if (customQuery != null) {
wikidataQuery = customQuery;
} else if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq");
} else {
wikidataQuery = FileUtils.readFromResource(
"/queries/rectangle_query_for_nearby_monuments.rq");
}
final double westCornerLat = screenTopRight.getLatitude();
final double westCornerLong = screenTopRight.getLongitude();
final double eastCornerLat = screenBottomLeft.getLatitude();
final double eastCornerLong = screenBottomLeft.getLongitude();
final String query = wikidataQuery
.replace("${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat))
.replace("${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong))
.replace("${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat))
.replace("${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong))
.replace("${LANG}", language);
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
final Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
final String json = response.body().string();
final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
final List<Place> places = new ArrayList<>();
for (final NearbyResultItem item : bindings) {
final Place placeFromNearbyItem = Place.from(item);
if (shouldQueryForMonuments && item.getMonument() != null) {
placeFromNearbyItem.setMonument(true);
} else {
placeFromNearbyItem.setMonument(false);
}
places.add(placeFromNearbyItem);
}
return places;
}
throw new Exception(response.message());
}
/**
* Retrieves a list of places based on the provided list of places and language.
*
* @param placeList A list of Place objects for which to fetch information.
* @param language The language code to use for the query.
* @return A list of Place objects with additional information retrieved from Wikidata, or null
* if an error occurs.
* @throws IOException If there is an issue with reading the resource file or executing the HTTP
* request.
*/
@Nullable
public List<Place> getPlaces(
final List<Place> placeList, final String language) throws IOException {
final String wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq");
String qids = "";
for (final Place place : placeList) {
qids += "\n" + ("wd:" + place.getWikiDataEntityId());
}
final String query = wikidataQuery
.replace("${ENTITY}", qids)
.replace("${LANG}", language);
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
if (response.isSuccessful()) {
final String json = response.body().string();
final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
final List<Place> places = new ArrayList<>();
for (final NearbyResultItem item : bindings) {
final Place placeFromNearbyItem = Place.from(item);
places.add(placeFromNearbyItem);
}
return places;
} else {
throw new IOException("Unexpected response code: " + response.code());
}
}
}
/**
* Make API Call to get Places
*
* @param leftLatLng Left lat long
* @param rightLatLng Right lat long
* @return
* @throws Exception
*/
@Nullable
public String getPlacesAsKML(final LatLng leftLatLng, final LatLng rightLatLng)
throws Exception {
String kmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<!--Created by Wikimedia Commons Android app -->\n" +
"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n" +
" <Document>";
List<PlaceBindings> placeBindings = runQuery(leftLatLng,
rightLatLng);
if (placeBindings != null) {
for (PlaceBindings item : placeBindings) {
if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) {
String input = item.getLocation().getValue();
Pattern pattern = Pattern.compile(
"Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String longStr = matcher.group(1);
String latStr = matcher.group(2);
String itemUrl = item.getItem().getValue();
String itemName = item.getLabel().getValue().replace("&", "&amp;");
String itemLatitude = latStr;
String itemLongitude = longStr;
String itemClass = item.getClas().getValue();
String formattedItemName =
!itemClass.isEmpty() ? itemName + " (" + itemClass + ")"
: itemName;
String kmlEntry = "\n <Placemark>\n" +
" <name>" + formattedItemName + "</name>\n" +
" <description>" + itemUrl + "</description>\n" +
" <Point>\n" +
" <coordinates>" + itemLongitude + ","
+ itemLatitude
+ "</coordinates>\n" +
" </Point>\n" +
" </Placemark>";
kmlString = kmlString + kmlEntry;
} else {
Timber.e("No match found");
}
}
}
}
kmlString = kmlString + "\n </Document>\n" +
"</kml>\n";
return kmlString;
}
/**
* Make API Call to get Places
*
* @param leftLatLng Left lat long
* @param rightLatLng Right lat long
* @return
* @throws Exception
*/
@Nullable
public String getPlacesAsGPX(final LatLng leftLatLng, final LatLng rightLatLng)
throws Exception {
String gpxString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<gpx\n" +
" version=\"1.0\"\n" +
" creator=\"Wikimedia Commons Android app\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xmlns=\"http://www.topografix.com/GPX/1/0\"\n" +
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">"
+ "\n<bounds minlat=\"$MIN_LATITUDE\" minlon=\"$MIN_LONGITUDE\" maxlat=\"$MAX_LATITUDE\" maxlon=\"$MAX_LONGITUDE\"/>";
List<PlaceBindings> placeBindings = runQuery(leftLatLng, rightLatLng);
if (placeBindings != null) {
for (PlaceBindings item : placeBindings) {
if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) {
String input = item.getLocation().getValue();
Pattern pattern = Pattern.compile(
"Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String longStr = matcher.group(1);
String latStr = matcher.group(2);
String itemUrl = item.getItem().getValue();
String itemName = item.getLabel().getValue().replace("&", "&amp;");
String itemLatitude = latStr;
String itemLongitude = longStr;
String itemClass = item.getClas().getValue();
String formattedItemName =
!itemClass.isEmpty() ? itemName + " (" + itemClass + ")"
: itemName;
String gpxEntry =
"\n <wpt lat=\"" + itemLatitude + "\" lon=\"" + itemLongitude
+ "\">\n" +
" <name>" + itemName + "</name>\n" +
" <url>" + itemUrl + "</url>\n" +
" </wpt>";
gpxString = gpxString + gpxEntry;
} else {
Timber.e("No match found");
}
}
}
}
gpxString = gpxString + "\n</gpx>";
return gpxString;
}
private List<PlaceBindings> runQuery(final LatLng currentLatLng, final LatLng nextLatLng)
throws IOException {
final String wikidataQuery = FileUtils.readFromResource("/queries/places_query.rq");
final String query = wikidataQuery
.replace("${LONGITUDE}",
String.format(Locale.ROOT, "%.2f", currentLatLng.getLongitude()))
.replace("${LATITUDE}", String.format(Locale.ROOT, "%.4f", currentLatLng.getLatitude()))
.replace("${NEXT_LONGITUDE}",
String.format(Locale.ROOT, "%.4f", nextLatLng.getLongitude()))
.replace("${NEXT_LATITUDE}",
String.format(Locale.ROOT, "%.4f", nextLatLng.getLatitude()));
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
final Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
final String json = response.body().string();
final ItemsClass item = gson.fromJson(json, ItemsClass.class);
return item.getResults().getBindings();
} else {
return null;
}
}
/**
* Make API Call to get Nearby Places Implementation does not expects a custom query
*
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @return
* @throws Exception
*/
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius)
throws Exception {
return getNearbyPlaces(cur, language, radius, null);
}
/**
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
* bridge -> suspended bridge, aqueduct, etc
*/
public Single<List<DepictedItem>> getChildDepictions(String qid, int startPosition,
int limit) throws IOException {
return depictedItemsFrom(
sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq"));
}
/**
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
* bridge -> suspended bridge, aqueduct, etc
*/
public Single<List<DepictedItem>> getParentDepictions(String qid, int startPosition,
int limit) throws IOException {
return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
"/queries/parentclasses_query.rq"));
}
private Single<List<DepictedItem>> depictedItemsFrom(Request request) {
return depictsClient.toDepictions(Single.fromCallable(() -> {
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
return gson.fromJson(body.string(), SparqlResponse.class);
}
}).doOnError(Timber::e));
}
@NotNull
private Request sparqlQuery(String qid, int startPosition, int limit, String fileName)
throws IOException {
String query = FileUtils.readFromResource(fileName)
.replace("${QID}", qid)
.replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
.replace("${LIMIT}", "" + limit)
.replace("${OFFSET}", "" + startPosition);
HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
return new Request.Builder()
.url(urlBuilder.build())
.build();
}
public Single<CampaignResponseDTO> getCampaigns() {
return Single.fromCallable(() -> {
Request request = new Request.Builder().url(campaignsUrl)
.build();
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.body() != null && response.isSuccessful()) {
String json = response.body().string();
if (json == null) {
return null;
}
return gson.fromJson(json, CampaignResponseDTO.class);
}
return null;
});
}
}

View file

@ -0,0 +1,543 @@
package fr.free.nrw.commons.mwapi
import android.text.TextUtils
import com.google.gson.Gson
import fr.free.nrw.commons.campaigns.CampaignResponseDTO
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.model.ItemsClass
import fr.free.nrw.commons.nearby.model.NearbyResponse
import fr.free.nrw.commons.nearby.model.PlaceBindings
import fr.free.nrw.commons.profile.achievements.FeaturedImages
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse
import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse
import fr.free.nrw.commons.upload.FileUtils
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse
import io.reactivex.Observable
import io.reactivex.Single
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import timber.log.Timber
import java.io.IOException
import java.util.Locale
import java.util.regex.Pattern
import javax.inject.Inject
import javax.inject.Singleton
/**
* Test methods in ok http api client
*/
@Singleton
class OkHttpJsonApiClient @Inject constructor(
private val okHttpClient: OkHttpClient,
private val depictsClient: DepictsClient,
private val wikiMediaToolforgeUrl: HttpUrl,
private val sparqlQueryUrl: String,
private val campaignsUrl: String,
private val gson: Gson
) {
fun getLeaderboard(
userName: String, duration: String?,
category: String?, limit: String?, offset: String?
): Observable<LeaderboardResponse> {
val fetchLeaderboardUrlTemplate =
wikiMediaToolforgeUrl.toString() + LeaderboardConstants.LEADERBOARD_END_POINT
val url = String.format(Locale.ENGLISH,
fetchLeaderboardUrlTemplate, userName, duration, category, limit, offset)
val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("user", userName)
.addQueryParameter("duration", duration)
.addQueryParameter("category", category)
.addQueryParameter("limit", limit)
.addQueryParameter("offset", offset)
Timber.i("Url %s", urlBuilder.toString())
val request: Request = Request.Builder()
.url(urlBuilder.toString())
.build()
return Observable.fromCallable({
val response: Response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json: String = response.body!!.string()
Timber.d("Response for leaderboard is %s", json)
try {
return@fromCallable gson.fromJson<LeaderboardResponse>(
json,
LeaderboardResponse::class.java
)
} catch (e: Exception) {
return@fromCallable LeaderboardResponse()
}
}
LeaderboardResponse()
})
}
fun setAvatar(username: String, avatar: String?): Single<UpdateAvatarResponse?> {
val urlTemplate = wikiMediaToolforgeUrl
.toString() + LeaderboardConstants.UPDATE_AVATAR_END_POINT
return Single.fromCallable<UpdateAvatarResponse?>({
val url = String.format(Locale.ENGLISH, urlTemplate, username, avatar)
val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("user", username)
.addQueryParameter("avatar", avatar)
Timber.i("Url %s", urlBuilder.toString())
val request: Request = Request.Builder()
.url(urlBuilder.toString())
.build()
val response: Response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json: String = response.body!!.string() ?: return@fromCallable null
try {
return@fromCallable gson.fromJson<UpdateAvatarResponse>(
json,
UpdateAvatarResponse::class.java
)
} catch (e: Exception) {
return@fromCallable UpdateAvatarResponse()
}
}
null
})
}
fun getUploadCount(userName: String?): Single<Int> {
val urlBuilder: HttpUrl.Builder = wikiMediaToolforgeUrl.newBuilder()
.addPathSegments("uploadsbyuser.py")
.addQueryParameter("user", userName)
if (isBetaFlavour) {
urlBuilder.addQueryParameter("labs", "commonswiki")
}
val request: Request = Request.Builder()
.url(urlBuilder.build())
.build()
return Single.fromCallable<Int>({
val response: Response = okHttpClient.newCall(request).execute()
if (response != null && response.isSuccessful) {
val responseBody = response.body
if (null != responseBody) {
val responseBodyString = responseBody.string().trim { it <= ' ' }
if (!TextUtils.isEmpty(responseBodyString)) {
try {
return@fromCallable responseBodyString.toInt()
} catch (e: NumberFormatException) {
Timber.e(e)
}
}
}
}
0
})
}
fun getWikidataEdits(userName: String?): Single<Int> {
val urlBuilder: HttpUrl.Builder = wikiMediaToolforgeUrl.newBuilder()
.addPathSegments("wikidataedits.py")
.addQueryParameter("user", userName)
if (isBetaFlavour) {
urlBuilder.addQueryParameter("labs", "commonswiki")
}
val request: Request = Request.Builder()
.url(urlBuilder.build())
.build()
return Single.fromCallable<Int>({
val response: Response = okHttpClient.newCall(request).execute()
if (response != null && response.isSuccessful && response.body != null) {
var json: String = response.body!!.string()
// Extract JSON from response
json = json.substring(json.indexOf('{'))
val countResponse = gson
.fromJson(
json,
GetWikidataEditCountResponse::class.java
)
if (null != countResponse) {
return@fromCallable countResponse.wikidataEditCount
}
}
0
})
}
fun getAchievements(userName: String?): Single<FeedbackResponse?> {
val suffix = if (isBetaFlavour) "/feedback.py?labs=commonswiki" else "/feedback.py"
val fetchAchievementUrlTemplate = wikiMediaToolforgeUrl.toString() + suffix
return Single.fromCallable<FeedbackResponse?>({
val url = String.format(
Locale.ENGLISH,
fetchAchievementUrlTemplate,
userName
)
val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("user", userName)
val request: Request = Request.Builder()
.url(urlBuilder.toString())
.build()
val response: Response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
var json: String = response.body!!.string()
// Extract JSON from response
json = json.substring(json.indexOf('{'))
Timber.d("Response for achievements is %s", json)
try {
return@fromCallable gson.fromJson<FeedbackResponse>(
json,
FeedbackResponse::class.java
)
} catch (e: Exception) {
return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "")
}
}
null
})
}
@JvmOverloads
@Throws(Exception::class)
fun getNearbyPlaces(
cur: LatLng, language: String, radius: Double,
customQuery: String? = null
): List<Place>? {
Timber.d("Fetching nearby items at radius %s", radius)
Timber.d("CUSTOM_SPARQL: %s", (customQuery != null).toString())
val wikidataQuery: String = if (customQuery != null) {
customQuery
} else {
FileUtils.readFromResource("/queries/radius_query_for_upload_wizard.rq")
}
val query = wikidataQuery
.replace("\${RAD}", String.format(Locale.ROOT, "%.2f", radius))
.replace("\${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude))
.replace("\${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude))
.replace("\${LANG}", language)
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json")
val request: Request = Request.Builder()
.url(urlBuilder.build())
.build()
val response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json = response.body!!.string()
val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java)
val bindings = nearbyResponse.results.bindings
val places: MutableList<Place> = ArrayList()
for (item in bindings) {
val placeFromNearbyItem = Place.from(item)
placeFromNearbyItem.isMonument = false
places.add(placeFromNearbyItem)
}
return places
}
throw Exception(response.message)
}
@Throws(Exception::class)
fun getNearbyPlaces(
screenTopRight: LatLng,
screenBottomLeft: LatLng, language: String,
shouldQueryForMonuments: Boolean, customQuery: String?
): List<Place>? {
Timber.d("CUSTOM_SPARQL: %s", (customQuery != null).toString())
val wikidataQuery: String = if (customQuery != null) {
customQuery
} else if (!shouldQueryForMonuments) {
FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq")
} else {
FileUtils.readFromResource("/queries/rectangle_query_for_nearby_monuments.rq")
}
val westCornerLat = screenTopRight.latitude
val westCornerLong = screenTopRight.longitude
val eastCornerLat = screenBottomLeft.latitude
val eastCornerLong = screenBottomLeft.longitude
val query = wikidataQuery
.replace("\${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat))
.replace("\${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong))
.replace("\${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat))
.replace("\${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong))
.replace("\${LANG}", language)
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json")
val request: Request = Request.Builder()
.url(urlBuilder.build())
.build()
val response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json = response.body!!.string()
val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java)
val bindings = nearbyResponse.results.bindings
val places: MutableList<Place> = ArrayList()
for (item in bindings) {
val placeFromNearbyItem = Place.from(item)
if (shouldQueryForMonuments && item.getMonument() != null) {
placeFromNearbyItem.isMonument = true
} else {
placeFromNearbyItem.isMonument = false
}
places.add(placeFromNearbyItem)
}
return places
}
throw Exception(response.message)
}
@Throws(IOException::class)
fun getPlaces(
placeList: List<Place>, language: String
): List<Place>? {
val wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq")
var qids = ""
for (place in placeList) {
qids += """
${"wd:" + place.wikiDataEntityId}"""
}
val query = wikidataQuery
.replace("\${ENTITY}", qids)
.replace("\${LANG}", language)
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json")
val request: Request = Request.Builder().url(urlBuilder.build()).build()
okHttpClient.newCall(request).execute().use { response ->
if (response.isSuccessful) {
val json = response.body!!.string()
val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java)
val bindings = nearbyResponse.results.bindings
val places: MutableList<Place> = ArrayList()
for (item in bindings) {
val placeFromNearbyItem = Place.from(item)
places.add(placeFromNearbyItem)
}
return places
} else {
throw IOException("Unexpected response code: " + response.code)
}
}
}
@Throws(Exception::class)
fun getPlacesAsKML(leftLatLng: LatLng, rightLatLng: LatLng): String? {
var kmlString = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--Created by Wikimedia Commons Android app -->
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>"""
val placeBindings = runQuery(
leftLatLng,
rightLatLng
)
if (placeBindings != null) {
for ((item1, label, location, clas) in placeBindings) {
if (item1 != null && label != null && clas != null) {
val input = location.value
val pattern = Pattern.compile(
"Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"
)
val matcher = pattern.matcher(input)
if (matcher.find()) {
val longStr = matcher.group(1)
val latStr = matcher.group(2)
val itemUrl = item1.value
val itemName = label.value.replace("&", "&amp;")
val itemLatitude = latStr
val itemLongitude = longStr
val itemClass = clas.value
val formattedItemName =
if (!itemClass.isEmpty())
"$itemName ($itemClass)"
else
itemName
val kmlEntry = ("""
<Placemark>
<name>$formattedItemName</name>
<description>$itemUrl</description>
<Point>
<coordinates>$itemLongitude,$itemLatitude</coordinates>
</Point>
</Placemark>""")
kmlString = kmlString + kmlEntry
} else {
Timber.e("No match found")
}
}
}
}
kmlString = """$kmlString
</Document>
</kml>
"""
return kmlString
}
@Throws(Exception::class)
fun getPlacesAsGPX(leftLatLng: LatLng, rightLatLng: LatLng): String? {
var gpxString = ("""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<gpx
version="1.0"
creator="Wikimedia Commons Android app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.topografix.com/GPX/1/0"
xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
<bounds minlat="${"$"}MIN_LATITUDE" minlon="${"$"}MIN_LONGITUDE" maxlat="${"$"}MAX_LATITUDE" maxlon="${"$"}MAX_LONGITUDE"/>""")
val placeBindings = runQuery(leftLatLng, rightLatLng)
if (placeBindings != null) {
for ((item1, label, location, clas) in placeBindings) {
if (item1 != null && label != null && clas != null) {
val input = location.value
val pattern = Pattern.compile(
"Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"
)
val matcher = pattern.matcher(input)
if (matcher.find()) {
val longStr = matcher.group(1)
val latStr = matcher.group(2)
val itemUrl = item1.value
val itemName = label.value.replace("&", "&amp;")
val itemLatitude = latStr
val itemLongitude = longStr
val itemClass = clas.value
val formattedItemName = if (!itemClass.isEmpty())
"$itemName ($itemClass)"
else
itemName
val gpxEntry =
("""
<wpt lat="$itemLatitude" lon="$itemLongitude">
<name>$itemName</name>
<url>$itemUrl</url>
</wpt>""")
gpxString = gpxString + gpxEntry
} else {
Timber.e("No match found")
}
}
}
}
gpxString = "$gpxString\n</gpx>"
return gpxString
}
@Throws(IOException::class)
fun getChildDepictions(
qid: String, startPosition: Int,
limit: Int
): Single<List<DepictedItem>> =
depictedItemsFrom(sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq"))
@Throws(IOException::class)
fun getParentDepictions(
qid: String, startPosition: Int,
limit: Int
): Single<List<DepictedItem>> = depictedItemsFrom(
sparqlQuery(
qid,
startPosition,
limit,
"/queries/parentclasses_query.rq"
)
)
fun getCampaigns(): Single<CampaignResponseDTO> {
return Single.fromCallable<CampaignResponseDTO?>({
val request: Request = Request.Builder().url(campaignsUrl).build()
val response: Response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json: String = response.body!!.string()
return@fromCallable gson.fromJson<CampaignResponseDTO>(
json,
CampaignResponseDTO::class.java
)
}
null
})
}
private fun depictedItemsFrom(request: Request): Single<List<DepictedItem>> {
return depictsClient.toDepictions(Single.fromCallable({
okHttpClient.newCall(request).execute().body.use { body ->
return@fromCallable gson.fromJson<SparqlResponse>(
body!!.string(),
SparqlResponse::class.java
)
}
}).doOnError({ t: Throwable? -> Timber.e(t) }))
}
@Throws(IOException::class)
private fun sparqlQuery(
qid: String,
startPosition: Int,
limit: Int,
fileName: String
): Request {
val query = FileUtils.readFromResource(fileName)
.replace("\${QID}", qid)
.replace("\${LANG}", "\"" + Locale.getDefault().language + "\"")
.replace("\${LIMIT}", "" + limit)
.replace("\${OFFSET}", "" + startPosition)
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json")
return Request.Builder().url(urlBuilder.build()).build()
}
@Throws(IOException::class)
private fun runQuery(currentLatLng: LatLng, nextLatLng: LatLng): List<PlaceBindings>? {
val wikidataQuery = FileUtils.readFromResource("/queries/places_query.rq")
val query = wikidataQuery
.replace("\${LONGITUDE}", String.format(Locale.ROOT, "%.2f", currentLatLng.longitude))
.replace("\${LATITUDE}", String.format(Locale.ROOT, "%.4f", currentLatLng.latitude))
.replace("\${NEXT_LONGITUDE}", String.format(Locale.ROOT, "%.4f", nextLatLng.longitude))
.replace("\${NEXT_LATITUDE}", String.format(Locale.ROOT, "%.4f", nextLatLng.latitude))
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json")
val request: Request = Request.Builder().url(urlBuilder.build()).build()
val response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json = response.body!!.string()
val item = gson.fromJson(json, ItemsClass::class.java)
return item.results.bindings
} else {
return null
}
}
}

View file

@ -194,7 +194,7 @@ class FileProcessor
requireNotNull(imageCoordinates.decimalCoords)
compositeDisposable.add(
apiCall
.request(imageCoordinates.decimalCoords)
.request(imageCoordinates.decimalCoords!!)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(
@ -220,7 +220,7 @@ class FileProcessor
.concatMap {
Observable.fromCallable {
okHttpJsonApiClient.getNearbyPlaces(
imageCoordinates.latLng,
imageCoordinates.latLng!!,
Locale.getDefault().language,
it,
)

View file

@ -49,13 +49,13 @@ class CampaignsPresenterTest {
campaignsSingle = Single.just(campaignResponseDTO)
campaignsPresenter = CampaignsPresenter(okHttpJsonApiClient, testScheduler, testScheduler)
campaignsPresenter.onAttachView(view)
Mockito.`when`(okHttpJsonApiClient.campaigns).thenReturn(campaignsSingle)
Mockito.`when`(okHttpJsonApiClient.getCampaigns()).thenReturn(campaignsSingle)
}
@Test
fun getCampaignsTestNoCampaigns() {
campaignsPresenter.getCampaigns()
verify(okHttpJsonApiClient).campaigns
verify(okHttpJsonApiClient).getCampaigns()
testScheduler.triggerActions()
verify(view).showCampaigns(null)
}
@ -77,7 +77,7 @@ class CampaignsPresenterTest {
Mockito.`when`(campaign.endDate).thenReturn(endDateString)
Mockito.`when`(campaign.startDate).thenReturn(startDateString)
Mockito.`when`(campaignResponseDTO.campaigns).thenReturn(campaigns)
verify(okHttpJsonApiClient).campaigns
verify(okHttpJsonApiClient).getCampaigns()
testScheduler.triggerActions()
verify(view).showCampaigns(campaign)
}