mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-11-04 00:33:55 +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