mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge pull request #1449 from psh/consolidate-networking-libraries-step-1
Code cleanup + automated testing around GPS Categories
This commit is contained in:
commit
f0b8039778
21 changed files with 619 additions and 313 deletions
|
|
@ -11,7 +11,6 @@ dependencies {
|
||||||
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||||
implementation 'in.yuvi:http.fluent:1.3'
|
implementation 'in.yuvi:http.fluent:1.3'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
||||||
implementation 'com.android.volley:volley:1.0.0'
|
|
||||||
implementation 'ch.acra:acra:4.9.2'
|
implementation 'ch.acra:acra:4.9.2'
|
||||||
implementation 'org.mediawiki:api:1.3'
|
implementation 'org.mediawiki:api:1.3'
|
||||||
implementation 'commons-codec:commons-codec:1.10'
|
implementation 'commons-codec:commons-codec:1.10'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-keep class org.apache.http.** { *; }
|
-keep class org.apache.http.** { *; }
|
||||||
-dontwarn org.apache.http.**
|
-dontwarn org.apache.http.**
|
||||||
-keep class fr.free.nrw.commons.upload.MwVolleyApi$Page {*;}
|
|
||||||
-keep class android.support.v7.widget.ShareActionProvider { *; }
|
-keep class android.support.v7.widget.ShareActionProvider { *; }
|
||||||
|
|
@ -7,18 +7,25 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class CacheController {
|
public class CacheController {
|
||||||
|
|
||||||
|
private final GpsCategoryModel gpsCategoryModel;
|
||||||
|
private final QuadTree<List<String>> quadTree;
|
||||||
private double x, y;
|
private double x, y;
|
||||||
private QuadTree<List<String>> quadTree;
|
|
||||||
private double xMinus, xPlus, yMinus, yPlus;
|
private double xMinus, xPlus, yMinus, yPlus;
|
||||||
|
|
||||||
private static final int EARTH_RADIUS = 6378137;
|
private static final int EARTH_RADIUS = 6378137;
|
||||||
|
|
||||||
public CacheController() {
|
@Inject
|
||||||
|
CacheController(GpsCategoryModel gpsCategoryModel) {
|
||||||
|
this.gpsCategoryModel = gpsCategoryModel;
|
||||||
quadTree = new QuadTree<>(-180, -90, +180, +90);
|
quadTree = new QuadTree<>(-180, -90, +180, +90);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,8 +38,8 @@ public class CacheController {
|
||||||
|
|
||||||
public void cacheCategory() {
|
public void cacheCategory() {
|
||||||
List<String> pointCatList = new ArrayList<>();
|
List<String> pointCatList = new ArrayList<>();
|
||||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
if (gpsCategoryModel.getGpsCatExists()) {
|
||||||
pointCatList.addAll(MwVolleyApi.getGpsCat());
|
pointCatList.addAll(gpsCategoryModel.getCategoryList());
|
||||||
Timber.d("Categories being cached: %s", pointCatList);
|
Timber.d("Categories being cached: %s", pointCatList);
|
||||||
} else {
|
} else {
|
||||||
Timber.d("No categories found, so no categories cached");
|
Timber.d("No categories found, so no categories cached");
|
||||||
|
|
@ -65,7 +72,7 @@ public class CacheController {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
|
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
|
||||||
public void convertCoordRange() {
|
private void convertCoordRange() {
|
||||||
//Position, decimal degrees
|
//Position, decimal degrees
|
||||||
double lat = y;
|
double lat = y;
|
||||||
double lon = x;
|
double lon = x;
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
|
@ -73,6 +73,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
@Inject @Named("prefs") SharedPreferences prefsPrefs;
|
@Inject @Named("prefs") SharedPreferences prefsPrefs;
|
||||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||||
@Inject CategoryDao categoryDao;
|
@Inject CategoryDao categoryDao;
|
||||||
|
@Inject GpsCategoryModel gpsCategoryModel;
|
||||||
|
|
||||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||||
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
||||||
|
|
@ -253,7 +254,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<CategoryItem> defaultCategories() {
|
private Observable<CategoryItem> defaultCategories() {
|
||||||
|
|
||||||
Observable<CategoryItem> directCat = directCategories();
|
Observable<CategoryItem> directCat = directCategories();
|
||||||
if (hasDirectCategories) {
|
if (hasDirectCategories) {
|
||||||
Timber.d("Image has direct Cat");
|
Timber.d("Image has direct Cat");
|
||||||
|
|
@ -287,9 +287,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<CategoryItem> gpsCategories() {
|
private Observable<CategoryItem> gpsCategories() {
|
||||||
return Observable.fromIterable(
|
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
||||||
MwVolleyApi.GpsCatExists.getGpsCatExists()
|
|
||||||
? MwVolleyApi.getGpsCat() : new ArrayList<>())
|
|
||||||
.map(name -> new CategoryItem(name, false));
|
.map(name -> new CategoryItem(name, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,16 @@ import dagger.android.support.AndroidSupportInjectionModule;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
||||||
import fr.free.nrw.commons.delete.DeleteTask;
|
import fr.free.nrw.commons.delete.DeleteTask;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
||||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
|
||||||
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
||||||
|
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
CommonsApplicationModule.class,
|
CommonsApplicationModule.class,
|
||||||
|
NetworkingModule.class,
|
||||||
AndroidInjectionModule.class,
|
AndroidInjectionModule.class,
|
||||||
AndroidSupportInjectionModule.class,
|
AndroidSupportInjectionModule.class,
|
||||||
ActivityBuilderModule.class,
|
ActivityBuilderModule.class,
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,15 @@ import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.util.LruCache;
|
import android.support.v4.util.LruCache;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||||
import fr.free.nrw.commons.upload.UploadController;
|
import fr.free.nrw.commons.upload.UploadController;
|
||||||
|
|
@ -33,7 +27,6 @@ import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MOD
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
public class CommonsApplicationModule {
|
public class CommonsApplicationModule {
|
||||||
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
|
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
|
||||||
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
|
|
||||||
|
|
||||||
private Context applicationContext;
|
private Context applicationContext;
|
||||||
|
|
||||||
|
|
@ -117,37 +110,12 @@ public class CommonsApplicationModule {
|
||||||
return new SessionManager(context, mediaWikiApi, sharedPreferences);
|
return new SessionManager(context, mediaWikiApi, sharedPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
public MediaWikiApi provideMediaWikiApi(Context context,
|
|
||||||
@Named("default_preferences") SharedPreferences defaultPreferences,
|
|
||||||
@Named("category_prefs") SharedPreferences categoryPrefs,
|
|
||||||
Gson gson) {
|
|
||||||
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public LocationServiceManager provideLocationServiceManager(Context context) {
|
public LocationServiceManager provideLocationServiceManager(Context context) {
|
||||||
return new LocationServiceManager(context);
|
return new LocationServiceManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
|
||||||
* @return returns a singleton Gson instance
|
|
||||||
*/
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
public Gson provideGson() {
|
|
||||||
return new Gson();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
public CacheController provideCacheController() {
|
|
||||||
return new CacheController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public DBOpenHelper provideDBOpenHelper(Context context) {
|
public DBOpenHelper provideDBOpenHelper(Context context) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package fr.free.nrw.commons.di;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
public class NetworkingModule {
|
||||||
|
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public OkHttpClient provideOkHttpClient() {
|
||||||
|
return new OkHttpClient.Builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public MediaWikiApi provideMediaWikiApi(Context context,
|
||||||
|
@Named("default_preferences") SharedPreferences defaultPreferences,
|
||||||
|
@Named("category_prefs") SharedPreferences categoryPrefs,
|
||||||
|
Gson gson) {
|
||||||
|
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("commons_mediawiki_url")
|
||||||
|
@NonNull
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
public HttpUrl provideMwUrl() {
|
||||||
|
return HttpUrl.parse(BuildConfig.COMMONS_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
||||||
|
* @return returns a singleton Gson instance
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public Gson provideGson() {
|
||||||
|
return new GsonBuilder().create();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
101
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java
Normal file
101
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
package fr.free.nrw.commons.mwapi;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
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 javax.inject.Named;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.mwapi.model.ApiResponse;
|
||||||
|
import fr.free.nrw.commons.mwapi.model.Page;
|
||||||
|
import fr.free.nrw.commons.mwapi.model.PageCategory;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
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 HttpUrl mwUrl;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CategoryApi(OkHttpClient okHttpClient, Gson gson,
|
||||||
|
@Named("commons_mediawiki_url") HttpUrl mwUrl) {
|
||||||
|
this.okHttpClient = okHttpClient;
|
||||||
|
this.mwUrl = mwUrl;
|
||||||
|
this.gson = gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<List<String>> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResponse apiResponse = gson.fromJson(body.charStream(), ApiResponse.class);
|
||||||
|
Set<String> categories = new LinkedHashSet<>();
|
||||||
|
if (apiResponse != null && apiResponse.hasPages()) {
|
||||||
|
for (Page page : apiResponse.query.pages) {
|
||||||
|
for (PageCategory category : page.getCategories()) {
|
||||||
|
categories.add(category.withoutPrefix());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(String coords) {
|
||||||
|
return mwUrl.newBuilder()
|
||||||
|
.addPathSegment("w")
|
||||||
|
.addPathSegment("api.php")
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
public class ApiResponse {
|
||||||
|
public Query query;
|
||||||
|
|
||||||
|
public ApiResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPages() {
|
||||||
|
return query != null && query.pages != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/src/main/java/fr/free/nrw/commons/mwapi/model/Page.java
Normal file
17
app/src/main/java/fr/free/nrw/commons/mwapi/model/Page.java
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public class Page {
|
||||||
|
public String title;
|
||||||
|
public PageCategory[] categories;
|
||||||
|
public PageCategory category;
|
||||||
|
|
||||||
|
public Page() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public PageCategory[] getCategories() {
|
||||||
|
return categories != null ? categories : new PageCategory[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
public class PageCategory {
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
public PageCategory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String withoutPrefix() {
|
||||||
|
return title != null ? title.replace("Category:", "") : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/src/main/java/fr/free/nrw/commons/mwapi/model/Query.java
Normal file
10
app/src/main/java/fr/free/nrw/commons/mwapi/model/Query.java
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
public class Query {
|
||||||
|
public Page[] pages;
|
||||||
|
|
||||||
|
public Query() {
|
||||||
|
pages = new Page[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class GpsCategoryModel {
|
||||||
|
private Set<String> categorySet;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GpsCategoryModel() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
categorySet = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getGpsCatExists() {
|
||||||
|
return !categorySet.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCategoryList() {
|
||||||
|
return new ArrayList<>(categorySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryList(List<String> categoryList) {
|
||||||
|
clear();
|
||||||
|
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String categoryString) {
|
||||||
|
categorySet.add(categoryString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.android.volley.Cache;
|
|
||||||
import com.android.volley.NetworkResponse;
|
|
||||||
import com.android.volley.Request;
|
|
||||||
import com.android.volley.RequestQueue;
|
|
||||||
import com.android.volley.Response;
|
|
||||||
import com.android.volley.VolleyError;
|
|
||||||
import com.android.volley.toolbox.HttpHeaderParser;
|
|
||||||
import com.android.volley.toolbox.JsonRequest;
|
|
||||||
import com.android.volley.toolbox.Volley;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the Volley library to implement asynchronous 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.
|
|
||||||
*/
|
|
||||||
public class MwVolleyApi {
|
|
||||||
|
|
||||||
private static RequestQueue REQUEST_QUEUE;
|
|
||||||
private static final Gson GSON = new GsonBuilder().create();
|
|
||||||
|
|
||||||
private static Set<String> categorySet;
|
|
||||||
private static List<String> categoryList;
|
|
||||||
|
|
||||||
private static final String MWURL = "https://commons.wikimedia.org/";
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
public MwVolleyApi(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
categorySet = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> getGpsCat() {
|
|
||||||
return categoryList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setGpsCat(List<String> cachedList) {
|
|
||||||
categoryList = new ArrayList<>();
|
|
||||||
categoryList.addAll(cachedList);
|
|
||||||
Timber.d("Setting GPS cats from cache: %s", categoryList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void request(String coords) {
|
|
||||||
String apiUrl = buildUrl(coords);
|
|
||||||
Timber.d("URL: %s", apiUrl);
|
|
||||||
|
|
||||||
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
|
||||||
new LogResponseListener<>(), new LogResponseErrorListener());
|
|
||||||
getQueue().add(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 String buildUrl(String coords) {
|
|
||||||
|
|
||||||
Uri.Builder builder = Uri.parse(MWURL).buildUpon();
|
|
||||||
|
|
||||||
builder.appendPath("w")
|
|
||||||
.appendPath("api.php")
|
|
||||||
.appendQueryParameter("action", "query")
|
|
||||||
.appendQueryParameter("prop", "categories|coordinates|pageprops")
|
|
||||||
.appendQueryParameter("format", "json")
|
|
||||||
.appendQueryParameter("clshow", "!hidden")
|
|
||||||
.appendQueryParameter("coprop", "type|name|dim|country|region|globe")
|
|
||||||
.appendQueryParameter("codistancefrompoint", coords)
|
|
||||||
.appendQueryParameter("generator", "geosearch")
|
|
||||||
.appendQueryParameter("ggscoord", coords)
|
|
||||||
.appendQueryParameter("ggsradius", "10000")
|
|
||||||
.appendQueryParameter("ggslimit", "10")
|
|
||||||
.appendQueryParameter("ggsnamespace", "6")
|
|
||||||
.appendQueryParameter("ggsprop", "type|name|dim|country|region|globe")
|
|
||||||
.appendQueryParameter("ggsprimary", "all")
|
|
||||||
.appendQueryParameter("formatversion", "2");
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized RequestQueue getQueue() {
|
|
||||||
if (REQUEST_QUEUE == null) {
|
|
||||||
REQUEST_QUEUE = Volley.newRequestQueue(context);
|
|
||||||
}
|
|
||||||
return REQUEST_QUEUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LogResponseListener<T> implements Response.Listener<T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(T response) {
|
|
||||||
Timber.d(response.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LogResponseErrorListener implements Response.ErrorListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
Timber.e(error.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
|
||||||
|
|
||||||
public QueryRequest(String url,
|
|
||||||
Response.Listener<QueryResponse> listener,
|
|
||||||
Response.ErrorListener errorListener) {
|
|
||||||
super(Request.Method.GET, url, null, listener, errorListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Response<QueryResponse> parseNetworkResponse(NetworkResponse response) {
|
|
||||||
String json = parseString(response);
|
|
||||||
QueryResponse queryResponse = GSON.fromJson(json, QueryResponse.class);
|
|
||||||
return Response.success(queryResponse, cacheEntry(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cache.Entry cacheEntry(NetworkResponse response) {
|
|
||||||
return HttpHeaderParser.parseCacheHeaders(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseString(NetworkResponse response) {
|
|
||||||
try {
|
|
||||||
return new String(response.data, HttpHeaderParser.parseCharset(response.headers));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
return new String(response.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GpsCatExists {
|
|
||||||
private static boolean gpsCatExists;
|
|
||||||
|
|
||||||
public static void setGpsCatExists(boolean gpsCat) {
|
|
||||||
gpsCatExists = gpsCat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getGpsCatExists() {
|
|
||||||
return gpsCatExists;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class QueryResponse {
|
|
||||||
private Query query = new Query();
|
|
||||||
|
|
||||||
private String printSet() {
|
|
||||||
if (categorySet == null || categorySet.isEmpty()) {
|
|
||||||
GpsCatExists.setGpsCatExists(false);
|
|
||||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
|
||||||
return "No collection of categories";
|
|
||||||
} else {
|
|
||||||
GpsCatExists.setGpsCatExists(true);
|
|
||||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
|
||||||
return "CATEGORIES FOUND" + categorySet.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (query != null) {
|
|
||||||
return "query=" + query.toString() + "\n" + printSet();
|
|
||||||
} else {
|
|
||||||
return "No pages found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Query {
|
|
||||||
private Page [] pages;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder("pages=" + "\n");
|
|
||||||
if (pages != null) {
|
|
||||||
for (Page page : pages) {
|
|
||||||
builder.append(page.toString());
|
|
||||||
builder.append("\n");
|
|
||||||
}
|
|
||||||
builder.replace(builder.length() - 1, builder.length(), "");
|
|
||||||
return builder.toString();
|
|
||||||
} else {
|
|
||||||
return "No pages found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Page {
|
|
||||||
private int pageid;
|
|
||||||
private int ns;
|
|
||||||
private String title;
|
|
||||||
private Category[] categories;
|
|
||||||
private Category category;
|
|
||||||
|
|
||||||
public Page() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder("PAGEID=" + pageid + " ns=" + ns + " title=" + title + "\n" + " CATEGORIES= ");
|
|
||||||
|
|
||||||
if (categories == null || categories.length == 0) {
|
|
||||||
builder.append("no categories exist\n");
|
|
||||||
} else {
|
|
||||||
for (Category category : categories) {
|
|
||||||
builder.append(category.toString());
|
|
||||||
builder.append("\n");
|
|
||||||
if (category != null) {
|
|
||||||
String categoryString = category.toString().replace("Category:", "");
|
|
||||||
categorySet.add(categoryString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryList = new ArrayList<>(categorySet);
|
|
||||||
builder.replace(builder.length() - 1, builder.length(), "");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Category {
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
|
@ -73,8 +74,10 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||||
|
import fr.free.nrw.commons.mwapi.CategoryApi;
|
||||||
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
@ -123,8 +126,12 @@ public class ShareActivity
|
||||||
@Inject
|
@Inject
|
||||||
ModifierSequenceDao modifierSequenceDao;
|
ModifierSequenceDao modifierSequenceDao;
|
||||||
@Inject
|
@Inject
|
||||||
|
CategoryApi apiCall;
|
||||||
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
|
@Inject
|
||||||
|
GpsCategoryModel gpsCategoryModel;
|
||||||
|
|
||||||
private String source;
|
private String source;
|
||||||
private String mimeType;
|
private String mimeType;
|
||||||
|
|
@ -685,8 +692,9 @@ public class ShareActivity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
||||||
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
|
* Then initiates the calls to MediaWiki API through an instance of CategpryApi.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
public void useImageCoords() {
|
public void useImageCoords() {
|
||||||
if (decimalCoords != null) {
|
if (decimalCoords != null) {
|
||||||
Timber.d("Decimal coords of image: %s", decimalCoords);
|
Timber.d("Decimal coords of image: %s", decimalCoords);
|
||||||
|
|
@ -699,20 +707,27 @@ public class ShareActivity
|
||||||
cacheController.setQtPoint(decLongitude, decLatitude);
|
cacheController.setQtPoint(decLongitude, decLatitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
MwVolleyApi apiCall = new MwVolleyApi(this);
|
|
||||||
|
|
||||||
List<String> displayCatList = cacheController.findCategory();
|
List<String> displayCatList = cacheController.findCategory();
|
||||||
boolean catListEmpty = displayCatList.isEmpty();
|
boolean catListEmpty = displayCatList.isEmpty();
|
||||||
|
|
||||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||||
if (catListEmpty) {
|
if (catListEmpty) {
|
||||||
cacheFound = false;
|
cacheFound = false;
|
||||||
apiCall.request(decimalCoords);
|
apiCall.request(decimalCoords)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.subscribe(
|
||||||
|
gpsCategoryModel::setCategoryList,
|
||||||
|
throwable -> {
|
||||||
|
Timber.e(throwable);
|
||||||
|
gpsCategoryModel.clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
||||||
} else {
|
} else {
|
||||||
cacheFound = true;
|
cacheFound = true;
|
||||||
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
|
Timber.d("Cache found, setting categoryList in model to %s", displayCatList);
|
||||||
MwVolleyApi.setGpsCat(displayCatList);
|
gpsCategoryModel.setCategoryList(displayCatList);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Timber.d("EXIF: no coords");
|
Timber.d("EXIF: no coords");
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package fr.free.nrw.commons
|
package fr.free.nrw.commons
|
||||||
|
|
||||||
|
import android.content.ContentProviderClient
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.support.v4.util.LruCache
|
import android.support.v4.util.LruCache
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.nhaarman.mockito_kotlin.mock
|
import com.nhaarman.mockito_kotlin.mock
|
||||||
import com.squareup.leakcanary.RefWatcher
|
import com.squareup.leakcanary.RefWatcher
|
||||||
import fr.free.nrw.commons.auth.AccountUtil
|
import fr.free.nrw.commons.auth.AccountUtil
|
||||||
import fr.free.nrw.commons.auth.SessionManager
|
import fr.free.nrw.commons.auth.SessionManager
|
||||||
import fr.free.nrw.commons.caching.CacheController
|
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper
|
import fr.free.nrw.commons.data.DBOpenHelper
|
||||||
import fr.free.nrw.commons.di.CommonsApplicationComponent
|
import fr.free.nrw.commons.di.CommonsApplicationComponent
|
||||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||||
|
|
@ -33,21 +32,30 @@ class TestCommonsApplication : CommonsApplication() {
|
||||||
override fun setupLeakCanary(): RefWatcher = RefWatcher.DISABLED
|
override fun setupLeakCanary(): RefWatcher = RefWatcher.DISABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModule(appContext) {
|
class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModule(appContext) {
|
||||||
val accountUtil: AccountUtil = mock()
|
val accountUtil: AccountUtil = mock()
|
||||||
val appSharedPreferences: SharedPreferences = mock()
|
val appSharedPreferences: SharedPreferences = mock()
|
||||||
val defaultSharedPreferences: SharedPreferences = mock()
|
val defaultSharedPreferences: SharedPreferences = mock()
|
||||||
val categorySharedPreferences: SharedPreferences = mock()
|
|
||||||
val otherSharedPreferences: SharedPreferences = mock()
|
val otherSharedPreferences: SharedPreferences = mock()
|
||||||
val uploadController: UploadController = mock()
|
val uploadController: UploadController = mock()
|
||||||
val mockSessionManager: SessionManager = mock()
|
val mockSessionManager: SessionManager = mock()
|
||||||
val mediaWikiApi: MediaWikiApi = mock()
|
|
||||||
val locationServiceManager: LocationServiceManager = mock()
|
val locationServiceManager: LocationServiceManager = mock()
|
||||||
val cacheController: CacheController = mock()
|
|
||||||
val mockDbOpenHelper: DBOpenHelper = mock()
|
val mockDbOpenHelper: DBOpenHelper = mock()
|
||||||
val nearbyPlaces: NearbyPlaces = mock()
|
val nearbyPlaces: NearbyPlaces = mock()
|
||||||
val lruCache: LruCache<String, String> = mock()
|
val lruCache: LruCache<String, String> = mock()
|
||||||
val gson: Gson = Gson()
|
val categoryClient: ContentProviderClient = mock()
|
||||||
|
val contributionClient: ContentProviderClient = mock()
|
||||||
|
val modificationClient: ContentProviderClient = mock()
|
||||||
|
val uploadPrefs: SharedPreferences = mock()
|
||||||
|
|
||||||
|
override fun provideCategoryContentProviderClient(context: Context?): ContentProviderClient = categoryClient
|
||||||
|
|
||||||
|
override fun provideContributionContentProviderClient(context: Context?): ContentProviderClient = contributionClient
|
||||||
|
|
||||||
|
override fun provideModificationContentProviderClient(context: Context?): ContentProviderClient = modificationClient
|
||||||
|
|
||||||
|
override fun providesDirectNearbyUploadPreferences(context: Context?): SharedPreferences = uploadPrefs
|
||||||
|
|
||||||
override fun providesAccountUtil(context: Context): AccountUtil = accountUtil
|
override fun providesAccountUtil(context: Context): AccountUtil = accountUtil
|
||||||
|
|
||||||
|
|
@ -61,12 +69,8 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
||||||
|
|
||||||
override fun providesSessionManager(context: Context, mediaWikiApi: MediaWikiApi, sharedPreferences: SharedPreferences): SessionManager = mockSessionManager
|
override fun providesSessionManager(context: Context, mediaWikiApi: MediaWikiApi, sharedPreferences: SharedPreferences): SessionManager = mockSessionManager
|
||||||
|
|
||||||
override fun provideMediaWikiApi(context: Context, sharedPreferences: SharedPreferences, categorySharedPreferences: SharedPreferences, gson: Gson): MediaWikiApi = mediaWikiApi
|
|
||||||
|
|
||||||
override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager
|
override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager
|
||||||
|
|
||||||
override fun provideCacheController(): CacheController = cacheController
|
|
||||||
|
|
||||||
override fun provideDBOpenHelper(context: Context): DBOpenHelper = mockDbOpenHelper
|
override fun provideDBOpenHelper(context: Context): DBOpenHelper = mockDbOpenHelper
|
||||||
|
|
||||||
override fun provideNearbyPlaces(): NearbyPlaces = nearbyPlaces
|
override fun provideNearbyPlaces(): NearbyPlaces = nearbyPlaces
|
||||||
|
|
|
||||||
178
app/src/test/kotlin/fr/free/nrw/commons/mwapi/CategoryApiTest.kt
Normal file
178
app/src/test/kotlin/fr/free/nrw/commons/mwapi/CategoryApiTest.kt
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
package fr.free.nrw.commons.mwapi
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import fr.free.nrw.commons.mwapi.model.Page
|
||||||
|
import fr.free.nrw.commons.mwapi.model.PageCategory
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CategoryApiTest {
|
||||||
|
private lateinit var server: MockWebServer
|
||||||
|
private lateinit var url: String
|
||||||
|
private lateinit var categoryApi: CategoryApi
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
server = MockWebServer()
|
||||||
|
url = "http://${server.hostName}:${server.port}/"
|
||||||
|
categoryApi = CategoryApi(OkHttpClient.Builder().build(), Gson(), HttpUrl.parse(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun teardown() {
|
||||||
|
server.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun apiReturnsEmptyListWhenError() {
|
||||||
|
server.enqueue(MockResponse().setResponseCode(400).setBody(""))
|
||||||
|
|
||||||
|
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun apiReturnsEmptyWhenTheresNoQuery() {
|
||||||
|
server.success(emptyMap())
|
||||||
|
|
||||||
|
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun apiReturnsEmptyWhenQueryHasNoPages() {
|
||||||
|
server.success(mapOf("query" to emptyMap<String, Any>()))
|
||||||
|
|
||||||
|
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun apiReturnsEmptyWhenQueryHasPagesButTheyreEmpty() {
|
||||||
|
server.success(mapOf("query" to
|
||||||
|
mapOf("pages" to emptyList<String>())))
|
||||||
|
|
||||||
|
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singlePageSingleCategory() {
|
||||||
|
server.success(mapOf("query" to
|
||||||
|
mapOf("pages" to listOf(
|
||||||
|
page(listOf("one"))
|
||||||
|
))))
|
||||||
|
|
||||||
|
val response = categoryApi.request("foo").blockingGet()
|
||||||
|
|
||||||
|
assertEquals(1, response.size)
|
||||||
|
assertEquals("one", response[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiplePagesSingleCategory() {
|
||||||
|
server.success(mapOf("query" to
|
||||||
|
mapOf("pages" to listOf(
|
||||||
|
page(listOf("one")),
|
||||||
|
page(listOf("two"))
|
||||||
|
))))
|
||||||
|
|
||||||
|
val response = categoryApi.request("foo").blockingGet()
|
||||||
|
|
||||||
|
assertEquals(2, response.size)
|
||||||
|
assertEquals("one", response[0])
|
||||||
|
assertEquals("two", response[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singlePageMultipleCategories() {
|
||||||
|
server.success(mapOf("query" to
|
||||||
|
mapOf("pages" to listOf(
|
||||||
|
page(listOf("one", "two"))
|
||||||
|
))))
|
||||||
|
|
||||||
|
val response = categoryApi.request("foo").blockingGet()
|
||||||
|
|
||||||
|
assertEquals(2, response.size)
|
||||||
|
assertEquals("one", response[0])
|
||||||
|
assertEquals("two", response[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiplePagesMultipleCategories() {
|
||||||
|
server.success(mapOf("query" to
|
||||||
|
mapOf("pages" to listOf(
|
||||||
|
page(listOf("one", "two")),
|
||||||
|
page(listOf("three", "four"))
|
||||||
|
))))
|
||||||
|
|
||||||
|
val response = categoryApi.request("foo").blockingGet()
|
||||||
|
|
||||||
|
assertEquals(4, response.size)
|
||||||
|
assertEquals("one", response[0])
|
||||||
|
assertEquals("two", response[1])
|
||||||
|
assertEquals("three", response[2])
|
||||||
|
assertEquals("four", response[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiplePagesMultipleCategories_duplicatesRemoved() {
|
||||||
|
server.success(mapOf("query" to
|
||||||
|
mapOf("pages" to listOf(
|
||||||
|
page(listOf("one", "two", "three")),
|
||||||
|
page(listOf("three", "four", "one"))
|
||||||
|
))))
|
||||||
|
|
||||||
|
val response = categoryApi.request("foo").blockingGet()
|
||||||
|
|
||||||
|
assertEquals(4, response.size)
|
||||||
|
assertEquals("one", response[0])
|
||||||
|
assertEquals("two", response[1])
|
||||||
|
assertEquals("three", response[2])
|
||||||
|
assertEquals("four", response[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun requestSendsWhatWeExpect() {
|
||||||
|
server.success(mapOf("query" to mapOf("pages" to emptyList<String>())))
|
||||||
|
|
||||||
|
val coords = "foo,bar"
|
||||||
|
categoryApi.request(coords).blockingGet()
|
||||||
|
|
||||||
|
server.takeRequest().let { request ->
|
||||||
|
assertEquals("GET", request.method)
|
||||||
|
assertEquals("/w/api.php", request.requestUrl.encodedPath())
|
||||||
|
request.requestUrl.let { url ->
|
||||||
|
assertEquals("query", url.queryParameter("action"))
|
||||||
|
assertEquals("categories|coordinates|pageprops", url.queryParameter("prop"))
|
||||||
|
assertEquals("json", url.queryParameter("format"))
|
||||||
|
assertEquals("!hidden", url.queryParameter("clshow"))
|
||||||
|
assertEquals("type|name|dim|country|region|globe", url.queryParameter("coprop"))
|
||||||
|
assertEquals(coords, url.queryParameter("codistancefrompoint"))
|
||||||
|
assertEquals("geosearch", url.queryParameter("generator"))
|
||||||
|
assertEquals(coords, url.queryParameter("ggscoord"))
|
||||||
|
assertEquals("10000", url.queryParameter("ggsradius"))
|
||||||
|
assertEquals("10", url.queryParameter("ggslimit"))
|
||||||
|
assertEquals("6", url.queryParameter("ggsnamespace"))
|
||||||
|
assertEquals("type|name|dim|country|region|globe", url.queryParameter("ggsprop"))
|
||||||
|
assertEquals("all", url.queryParameter("ggsprimary"))
|
||||||
|
assertEquals("2", url.queryParameter("formatversion"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun page(catList: List<String>) = Page().apply {
|
||||||
|
categories = catList.map {
|
||||||
|
PageCategory().apply {
|
||||||
|
title = "Category:$it"
|
||||||
|
}
|
||||||
|
}.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MockWebServer.success(response: Map<String, Any>) {
|
||||||
|
enqueue(MockResponse().setResponseCode(200).setBody(Gson().toJson(response)))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ApiResponseTest {
|
||||||
|
@Test
|
||||||
|
fun hasPages_whenQueryIsNull() {
|
||||||
|
val response = ApiResponse()
|
||||||
|
assertFalse(response.hasPages())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun hasPages_whenPagesIsNull() {
|
||||||
|
val response = ApiResponse()
|
||||||
|
response.query = Query()
|
||||||
|
response.query.pages = null
|
||||||
|
assertFalse(response.hasPages())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun hasPages_defaultsToSafeValue() {
|
||||||
|
val response = ApiResponse()
|
||||||
|
response.query = Query()
|
||||||
|
assertNotNull(response.query.pages)
|
||||||
|
assertTrue(response.hasPages())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class PageCategoryTest {
|
||||||
|
@Test
|
||||||
|
fun stripPrefix_whenPresent() {
|
||||||
|
val testObject = PageCategory()
|
||||||
|
testObject.title = "Category:Foo"
|
||||||
|
assertEquals("Foo", testObject.withoutPrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stripPrefix_prefixAbsent() {
|
||||||
|
val testObject = PageCategory()
|
||||||
|
testObject.title = "Foo_Bar"
|
||||||
|
assertEquals("Foo_Bar", testObject.withoutPrefix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model
|
||||||
|
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class PageTest {
|
||||||
|
@Test
|
||||||
|
fun categoriesDefaultToSafeValue() {
|
||||||
|
val page = Page()
|
||||||
|
assertNotNull(page.getCategories())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class GpsCategoryModelTest {
|
||||||
|
|
||||||
|
private lateinit var testObject : GpsCategoryModel
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
testObject = GpsCategoryModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun initiallyTheModelIsEmpty() {
|
||||||
|
assertFalse(testObject.gpsCatExists)
|
||||||
|
assertTrue(testObject.categoryList.isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addingCategoriesToTheModel() {
|
||||||
|
testObject.add("one")
|
||||||
|
assertTrue(testObject.gpsCatExists)
|
||||||
|
assertFalse(testObject.categoryList.isEmpty())
|
||||||
|
assertEquals(listOf("one"), testObject.categoryList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun duplicatesAreIgnored() {
|
||||||
|
testObject.add("one")
|
||||||
|
testObject.add("one")
|
||||||
|
assertEquals(listOf("one"), testObject.categoryList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun modelProtectsAgainstExternalModification() {
|
||||||
|
testObject.add("one")
|
||||||
|
|
||||||
|
val list = testObject.categoryList
|
||||||
|
list.add("two")
|
||||||
|
|
||||||
|
assertEquals(listOf("one"), testObject.categoryList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clearingTheModel() {
|
||||||
|
testObject.add("one")
|
||||||
|
|
||||||
|
testObject.clear()
|
||||||
|
assertFalse(testObject.gpsCatExists)
|
||||||
|
assertTrue(testObject.categoryList.isEmpty())
|
||||||
|
|
||||||
|
testObject.add("two")
|
||||||
|
assertEquals(listOf("two"), testObject.categoryList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun settingTheListHandlesNull() {
|
||||||
|
testObject.add("one")
|
||||||
|
|
||||||
|
testObject.categoryList = null
|
||||||
|
|
||||||
|
assertFalse(testObject.gpsCatExists)
|
||||||
|
assertTrue(testObject.categoryList.isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setttingTheListOverwritesExistingValues() {
|
||||||
|
testObject.add("one")
|
||||||
|
|
||||||
|
testObject.categoryList = listOf("two")
|
||||||
|
|
||||||
|
assertEquals(listOf("two"), testObject.categoryList)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue