mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 13:53:54 +01:00
Convert OkHttpJsonApiClient and CategoryApi to kotlin
This commit is contained in:
parent
33548fa57d
commit
79de03964e
7 changed files with 633 additions and 783 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
83
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt
Normal file
83
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt
Normal 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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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("&", "&");
|
||||
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("&", "&");
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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("&", "&")
|
||||
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("&", "&")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue